Weekly Rust Trivia is a problem-oriented series of articles that assist developers while learning Rust. Every article solves simple, everyday development tasks using the Rust standard library or leveraging popular and proven crates.

Question: How to read a .csv file in Rust?

The csv crate provides necessary functionality to work with .csv files in Rust. We can combine functionality provided by the crate with the fs module from Rusts standard library to solve this trivia. We also use the anyhow crate, to streamline error handling.

First let’s add the following dependencies using cargo add:

# Add anyhow as dependency
cargo add anyhow
# Add the csv crate as dependency
cargo add csv

Having the crates added to our project, we can move on and implement reading a .csv file:

use std::fs::File;
use anyhow::Result;
use csv::{ReaderBuilder, StringRecord};

fn read_csv_file(file_name: &str, has_headers: bool, delimiter: u8) -> Result<Vec<StringRecord>> {
    // Open the CSV file
    let file = File::open(file_name)?;

    let mut reader = ReaderBuilder::new()
        .has_headers(has_headers)
        .delimiter(delimiter)
        .from_reader(file);

    Ok(reader.records()
        .filter_map(|row| row.ok())
        .collect())
}

The read_csv_file function takes a file path, a boolean indicating if the .csv file has a row with headers, and the delimiter used in that particular .csv file as arguments and returns an Vec<StringRecord> containing all data from the underlying CSV file wrapped by a anyhow::Result<>.

The function starts by opening the file using the File::open() method. Next, the ReaderBuilder provided by the csv crate is used to specify the characteristics of the given .csv file. For example, we use the has_headers function to specify if the .csv file contains a row with headers. The delimiter function is also used to set the delimiter used in the particular .csv file.

Finally we use the from_reader function to create an instance of Reader<File>. This allows us convert all “okay-ish” rows into a simple Vec<StringRecord> which will can return back to the callee.

With a StringRecord, we can access individual fields of a particular row in the .csv file using the get function, or print the entire StringRecord to STDOUT.

We can call the read_csv_file function as shown below:

fn main() {
    let csv_file = "data/sample_without_headers.csv";
    let delimiter = b',';
    match read_csv_file(csv_file, false, delimiter) {
        Ok(rows) => {
            for row in &rows {
                println!("{:?}", row);
            }
            println!("\nTotal rows: {}", &rows.len());
            println!("Done!");
        }
        Err(e) => println!("Error: {}", e),
    }
}