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),
}
}