Работа с CSV в Rust: библиотеки csv, serde и эффективная обработка данных

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Работа с CSV в Rust: библиотеки, примеры кода и лучшие практики

CSV (Comma-Separated Values) — один из самых распространённых форматов для хранения табличных данных. В экосистеме Rust существует мощная и гибкая библиотека csv, которая в сочетании с крейтом serde позволяет удобно читать, записывать и трансформировать CSV-данные. В этой статье мы подробно разберём, как работать с CSV в Rust, начиная с базового парсинга и заканчивая сериализацией сложных структур.

Установка и подключение зависимостей

Для начала работы добавьте в файл Cargo.toml следующие зависимости:

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

Библиотека csv предоставляет низкоуровневый API для чтения и записи, а serde — автоматическую десериализацию строк CSV в структуры Rust.

Чтение CSV-файла построчно

Самый простой способ прочитать CSV — использовать Reader из крейта csv. Рассмотрим пример с файлом users.csv:

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

fn main() -> Result<(), Box<dyn Error>> {
    let mut rdr = Reader::from_path("users.csv")?;

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

Этот код выводит каждую строку в виде вектора строк StringRecord. Однако на практике чаще требуется работать с типизированными данными.

Десериализация в структуры с serde

Объявим структуру, соответствующую столбцам CSV:

use serde::Deserialize;

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

Теперь можно читать записи напрямую в экземпляры User:

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

fn main() -> Result<(), Box<dyn Error>> {
    let mut rdr = Reader::from_path("users.csv")?;

    for result in rdr.deserialize() {
        let user: User = result?;
        println!("{} ({} лет) из {}", user.name, user.age, user.city);
    }
    Ok(())
}

Библиотека автоматически сопоставляет заголовки CSV с полями структуры. Если порядок или имена столбцов отличаются, используйте атрибут #[serde(rename = "...")].

Запись CSV-файла

Создание CSV-файла выполняется с помощью Writer. Запись структурированных данных через serde:

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

#[derive(Debug, Serialize)]
struct Product {
    id: u32,
    name: String,
    price: f64,
}

fn main() -> Result<(), Box<dyn Error>> {
    let mut wtr = Writer::from_path("products.csv")?;

    let products = vec![
        Product { id: 1, name: "Ноутбук".into(), price: 75000.0 },
        Product { id: 2, name: "Мышь".into(), price: 1500.0 },
    ];

    for product in &products {
        wtr.serialize(product)?;
    }

    wtr.flush()?;
    println!("Файл products.csv успешно записан");
    Ok(())
}

Обратите внимание: Writer автоматически добавляет заголовки на основе имён полей структуры.

Настройка разделителей и кавычек

По умолчанию используется запятая и двойные кавычки. Если ваш файл использует точку с запятой (например, в европейской локали), настройте ReaderBuilder:

use csv::ReaderBuilder;

let mut rdr = ReaderBuilder::new()
    .delimiter(b';')
    .has_headers(true)
    .from_path("data.tsv")?;

Аналогично настраивается WriterBuilder для записи.

Обработка ошибок и невалидных данных

При работе с реальными данными часто встречаются пропущенные значения или неверные типы. Используйте Option<T> для необязательных полей:

#[derive(Debug, Deserialize)]
struct Record {
    name: String,
    age: Option<u32>, // возраст может отсутствовать
    email: String,
}

Для гибкой обработки ошибок можно перехватывать проблемные записи и логировать их

Рекомендаций