📖

rust-csvのつかいかた

2023/01/16に公開

はじめに

Rustにはかなり古くから整備されてきた超有能CSVパースライブラリが存在します.
こちらがcrates.ioのページです.
https://crates.io/crates/csv

公式のドキュメントはこちらです.
https://docs.rs/csv/latest/csv/

この記事の作成にあたっては、これらのドキュメントに加え、
以下のサイトを参考にさせていただきました.
https://qiita.com/algebroid/items/c456d4ec555ae04c7f92
https://shinshin86.hateblo.jp/entry/2022/08/07/195504

依存関係のインストール

cargo add csv

で依存関係に追加しましょう.

次のように追加されるはずです.

Cargo.toml
[dependencies]
csv = "1.1.6"

シンプルな使用方法

CSVをパースして表示するだけのごくシンプルなプログラムを考えます.
コマンドライン引数からファイルパスを読み取り、C言語でいうところのfopenを作成しファイルインスタンスを作成、CSVパーサーに渡しています.

main.rs
extern crate csv;

use std::env;
use std::error::Error;
use std::ffi::OsString;
use std::fs::File;
use std::process;

fn get_first_arg() -> Result<OsString, Box<dyn Error>> {
    match env::args_os().nth(1) {
        None => Err(From::from("expected 1 argument, but got none")),
        Some(file_path) => Ok(file_path),
    }
}

fn fopen(path: &OsString) -> Result<File, Box<dyn Error>> {
    let file = File::open(path)?;
    Ok(file)
}

fn csv_parse(file: File) -> Result<(), Box<dyn Error>> {
    let mut rdr = csv::Reader::from_reader(file);
    for result in rdr.records() {
        let record = result?;
        println!("{:?}", record);
    }
    Ok(())
}

fn main() {
    let file_path = match get_first_arg() {
        Ok(file_path) => file_path,
        Err(err) => {
            eprintln!("Error: {}", err);
            process::exit(1);
        }
    };
    
    match csv_parse(fopen(&file_path).unwrap()) {
        Ok(_) => println!("Successfully parsed CSV"),
        Err(err) => {
            eprintln!("Error: {}", err);
            process::exit(1);
        }
    }
}

このとき、返されているrecordの型はcsv::StringRecord構造体です.
https://docs.rs/csv/latest/csv/struct.StringRecord.html

1行目読み取られない問題

このCSVパーサー、1行目はタイトル行としてデフォルトで読み飛ばす設定になっているらしいです.
ReaderBuilder::has_headersの設定を書き換えると1行目も読み込めます

fn csv_parse(file: File) -> Result<(), Box<dyn Error>> {
    let mut rdr = csv::ReaderBuilder::new()
        .has_headers(false)
        .from_reader(file);
    for result in rdr.records() {
        let record = result?;
        println!("{:?}", record);
    }
    Ok(())
}

1行1列目だけ読み込む

fn csv_parse(file: File) -> Result<String, Box<dyn Error>> {
    let mut rdr = csv::ReaderBuilder::new()
        .has_headers(false)
        .from_reader(file);
    let first_line = rdr.records().next();
    match first_line {
        Some(Ok(record)) => Ok(record[0].to_owned()),
        Some(Err(err)) => Err(From::from(err)),
        None => Err(From::from("No data found in CSV file")),
    }
}

Discussion