Skip to content
Select theme

How to read a CSV file into structs

Short answer: Open the file with csv::Reader::from_path, then call deserialize() into a struct that derives Deserialize. The first row is the header, and its column names are matched against the struct’s field names — #[serde(rename = "...")] bridges the gap when they don’t match.

This continues the json-demo project from the earlier pages in this chapter — cargo new json-demo, with one more crate:

Cargo.toml
[dependencies]
csv = "1.4.0"
serde = { version = "1.0.229", features = ["derive"] }

Each block below writes the file it reads before reading it, so it runs on its own — in your own code the file is already there and you delete that line.

people.csv
name,age,email,signup
Ada Lovelace,36,[email protected],2024-01-15
Grace Hopper,45,[email protected],2024-02-20
src/main.rs
use serde::Deserialize;
use std::fs;
#[derive(Debug, Deserialize)]
struct Person {
name: String,
age: u8,
email: String,
}
fn main() {
fs::write(
"people.csv",
"name,age,email,signup\nAda Lovelace,36,[email protected],2024-01-15\nGrace Hopper,45,[email protected],2024-02-20\n",
)
.unwrap();
let mut reader = csv::Reader::from_path("people.csv").unwrap();
for person in reader.deserialize::<Person>() {
println!("{:?}", person.unwrap());
}
}
Person { name: "Ada Lovelace", age: 36, email: "[email protected]" }
Person { name: "Grace Hopper", age: 45, email: "[email protected]" }

deserialize() hands back an iterator of Result<Person, csv::Error>, one item per row with the header excluded. The header row names the fields, so a column the struct doesn’t have is ignored — signup never reaches Person, the same way an unknown JSON key is dropped. The mapping goes through the names, not the positions, so column order doesn’t matter either.

A CSV exported from a spreadsheet usually has capitals, spaces or punctuation in its header row, and the match is exact, case included. #[serde(rename = "...")] on the field is the fix — the same attribute the JSON page uses against an API you cannot change:

users.csv
age,Full Name,E-mail Address
36,Ada Lovelace,[email protected]
45,Grace Hopper,[email protected]
src/main.rs
use serde::Deserialize;
use std::fs;
#[derive(Debug, Deserialize)]
struct Person {
#[serde(rename = "Full Name")]
name: String,
age: u8,
#[serde(rename = "E-mail Address")]
email: String,
}
fn main() {
fs::write(
"users.csv",
"age,Full Name,E-mail Address\n36,Ada Lovelace,[email protected]\n45,Grace Hopper,[email protected]\n",
)
.unwrap();
let mut reader = csv::Reader::from_path("users.csv").unwrap();
for person in reader.deserialize::<Person>() {
println!("{:?}", person.unwrap());
}
}
Person { name: "Ada Lovelace", age: 36, email: "[email protected]" }
Person { name: "Grace Hopper", age: 45, email: "[email protected]" }

age needs no rename: it already matches. Note that the column order here is not the struct’s field order, and the records still come out right, because the header decides the mapping.

Capitalised headers with no rename do not fail silently. The error names the field it could not find, which is the hint that a rename is what’s missing:

src/main.rs
use serde::Deserialize;
use std::fs;
#[derive(Debug, Deserialize)]
struct Person {
name: String,
age: u8,
email: String,
}
fn main() {
fs::write(
"people.csv",
"Name,Age,Email\nAda Lovelace,36,[email protected]\n",
)
.unwrap();
let mut reader = csv::Reader::from_path("people.csv").unwrap();
for person in reader.deserialize::<Person>() {
match person {
Ok(person) => println!("{person:?}"),
Err(e) => println!("error: {e}"),
}
}
}
error: CSV deserialize error: record 1 (line: 2, byte: 15): missing field `name`

The position in the error — record 1 (line: 2, byte: 15) — points at the first data row, and nothing is printed before it, so a file whose header is wrong yields no records at all, not a file of default values. unwrap() on the item instead of the match above turns the same error into a panic, which is what a quick script usually does.

The next page in this chapter writes CSV from structs, where the quoting rules surprise people.