Работа с CSV в Rust: библиотека csv и практические примеры
Формат CSV (Comma-Separated Values) остаётся одним из самых популярных способов хранения табличных данных. В Rust для работы с CSV существует зрелая и высокопроизводительная библиотека csv, которая предоставляет удобный API для чтения, записи и обработки данных. В этой статье мы подробно разберём, как эффективно работать с CSV в Rust.
Установка и подключение библиотеки
Для начала добавьте зависимость в файл Cargo.toml:
[dependencies]
csv = "1.3"
Библиотека csv поддерживает работу с кодировками, настраиваемыми разделителями и кавычками, а также предоставляет возможность сериализации/десериализации данных через типажи serde.
Чтение CSV-файла
Самый простой способ прочитать CSV — использовать csv::Reader. Рассмотрим пример с файлом users.csv:
use std::error::Error;
use csv::ReaderBuilder;
fn main() -> Result<(), Box<dyn Error>> {
// Создаём ридер с настройками
let mut rdr = ReaderBuilder::new()
.has_headers(true) // первая строка — заголовки
.delimiter(b',') // разделитель — запятая
.from_path("users.csv")?;
// Выводим заголовки
let headers = rdr.headers()?;
println!("Headers: {:?}", headers);
// Итерируемся по записям
for result in rdr.records() {
let record = result?;
println!("{:?}", record);
}
Ok(())
}
Обработка записей с помощью десериализации
Для работы со структурированными данными удобно использовать десериализацию в пользовательские структуры. Подключите serde и serde_derive:
[dependencies]
csv = "1.3"
serde = { version = "1.0", features = ["derive"] }
Теперь можно определить структуру и читать данные напрямую:
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct User {
name: String,
age: u8,
email: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut rdr = csv::Reader::from_path("users.csv")?;
for result in rdr.deserialize() {
let user: User = result?;
println!("Пользователь: {} ({} лет) - {}", user.name, user.age, user.email);
}
Ok(())
}
Запись данных в CSV
Для записи используется csv::Writer. Пример записи структурированных данных:
use csv::Writer;
use serde::Serialize;
#[derive(Debug, Serialize)]
struct Product {
id: u32,
name: String,
price: f64,
in_stock: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut wtr = Writer::from_path("products.csv")?;
// Записываем заголовки
wtr.write_record(&["id", "name", "price", "in_stock"])?;
// Записываем данные через сериализацию
let products = vec![
Product { id: 1, name: "Ноутбук".into(), price: 75000.50, in_stock: true },
Product { id: 2, name: "Мышь".into(), price: 1500.00, in_stock: false },
];
for product in &products {
wtr.serialize(product)?;
}
wtr.flush()?;
println!("Данные записаны в products.csv");
Ok(())
}
Настройка разделителей и кавычек
Библиотека позволяет гибко настраивать формат. Например, для TSV (табуляция как разделитель):
let mut rdr = csv::ReaderBuilder::new()
.delimiter(b'\t')
.flexible(true) // разрешить разное количество полей
.quoting(false) // отключить кавычки
.from_reader(io::stdin());
Обработка ошибок и больших файлов
При работе с большими CSV-файлами важно учитывать производительность. csv использует буферизированное чтение и не загружает весь файл в память. Для обработки ошибок используйте паттерн Result:
fn process_csv(path: &str) -> Result<u64, Box<dyn std::error::Error>> {
let mut rdr = csv::ReaderBuilder::new()
.trim(csv::Trim::All) // обрезать пробелы
.from_path(path)?;
let mut count = 0u64;
for result in rdr.records() {
match result {
Ok(record) => {
count +=