Working with CSV in Rust: the csv library and practical examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

The csv library for working with CSV files in Rust



CSV (Comma-Separated Values) is one of the most common formats for storing tabular data. In Rust, there is a powerful, flexible, and performant library csv for working with CSV, which is part of the BurntSushi ecosystem (author of ripgrep and regex). It allows you to read, write, serialize, and deserialize data with minimal effort and maximum type safety.



Installation

Add the dependency to Cargo.toml:

[dependencies]csv = "1.3"


For serialization/deserialization of structs, you will also need serde:

[dependencies]csv = "1.3"serde = { version = "1.0", features = ["derive"] }


Main features of the library

  • Reading and writing with configurable delimiters, quotes, and escape characters.
  • Automatic header handling — working by column names.
  • Serialization/Deserialization via serde — converting CSV rows into strongly typed Rust structs.
  • Flexible error handling — error positioning (line number, column).
  • Streaming processing — working with large files without loading everything into memory.
  • Support for non-UTF8 encodings (via external crates, e.g., encoding).


Reading a CSV file

The simplest way to read CSV is to use Reader and an iterator over records.



Example 1: Reading without headers

use csv::ReaderBuilder;use std::error::Error;

fn read_csv_no_headers() -> Result<(), Box<dyn Error>> { let data = "name,age,cityAlice,30,LondonBob,25,Paris"; let mut reader = ReaderBuilder::new() .has_headers(false) .from_reader(data.as_bytes());

for result in reader.records() { let record = result?; println!("{:?}", record); } Ok(())}


Example 2: Reading with headers

use csv::Reader;use std::error::Error;

fn read_csv_with_headers() -> Result<(), Box<dyn Error>> { let data = "name,age,cityAlice,30,LondonBob,25,Paris"; let mut reader = Reader::from_reader(data.as_bytes());

for result in reader.deserialize() { let record: (String, u32, String) = result?; println!("{} is {} years old from {}", record.0, record.1, record.2); } Ok(())}


Using Serde for strong typing

The most convenient way is to define a struct annotated with #[derive(Deserialize)].



use serde::Deserialize;use csv::Reader;use std::error::Error;

#[derive(Debug, Deserialize)]struct Person { name: String, age: u32, city: String,}

fn read_typed_csv() -> Result<(), Box<dyn Error>> { let data = "name,age,cityAlice,30,LondonBob,25,Paris"; let mut reader = Reader::from_reader(data.as_bytes());

for result in reader.deserialize() { let person: Person = result?; println!("{:?}", person); } Ok(())}


This guarantees that the data will be automatically cast to the required types. If, for example, the age field contains the string "abc", the program will return an error indicating the line and column.



Writing a CSV file

The Writer is used for writing. You can write both raw strings and serialized structs.



Example: writing via a struct

use serde::Serialize;use csv::Writer;use std::error::Error;

#[derive(Debug, Serialize)]struct Person { name: String, age: u32, city: String,}

fn write_csv() -> Result<(), Box<dyn

Recommendations