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 the necessary functionality to work with .csv files in Rust. We can combine functionality provided by the crate with the fs module from Rust’s 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:

BASH
# 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:

RUST
use anyhow::Result;
use csv::{ReaderBuilder, StringRecord};

fn read_csv_file(file_name: &str, has_headers: bool, delimiter: u8) -> Result<Vec<StringRecord>> {
    let mut reader = ReaderBuilder::new()
        .has_headers(has_headers)
        .delimiter(delimiter)
        .from_path(file_name)?;

    let records = reader.records().collect::<Result<Vec<_>, _>>()?;
    Ok(records)
}

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 a Vec<StringRecord> containing all data from the underlying CSV file wrapped by an anyhow::Result<>.

The function starts with the ReaderBuilder provided by the csv crate, which 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 (if it does, the header row is not part of the returned records). The delimiter function is used to set the delimiter used in the particular .csv file.

Finally, we use the from_path function to open the file and create an instance of Reader<File>. This saves us from opening the file with File::open() ourselves. Next, we collect all rows into a Vec<StringRecord>, which we can return to the caller. Because we collect into a Result<Vec<_>, _>, the function fails with an error as soon as it encounters a malformed row (for example, a row with a different number of fields than the previous one) instead of silently skipping it.

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:

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

In case you also need to deal with files on disk, take a look at how to get all files in a directory or how to compute a SHA256 hash of a file .