El crate csv se combina con serde igual que el JSON: declaras una estructura y cada fila del archivo se convierte en un valor de ese tipo, con los números ya convertidos. Lee la cabecera por su cuenta y no hay que partir cadenas a mano.
También te puede interesar
La dependencia
cargo add csv cargo add serde --features derive
[dependencies]
csv = "1.4.0"
serde = { version = "1.0.229", features = ["derive"] }
Los ejemplos trabajan sobre este inventario.csv:
inventario.csvnombre,precio,unidades tornillos,1.50,120 tuercas,0.80,80 arandelas,0.25,45
Leer y escribir en el mismo programa
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
struct Articulo {
nombre: String,
precio: f64,
unidades: u32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut lector = csv::Reader::from_path("inventario.csv")?;
let mut total = 0.0;
for fila in lector.deserialize() {
let a: Articulo = fila?;
let valor = a.precio * a.unidades as f64;
println!("{:<12} {:>8.2}", a.nombre, valor);
total += valor;
}
println!("{:<12} {:>8.2}", "TOTAL", total);
let mut escritor = csv::Writer::from_path("salida.csv")?;
escritor.serialize(Articulo { nombre: "clavos".into(), precio: 0.10, unidades: 500 })?;
escritor.flush()?;
println!("\nsalida.csv escrito");
Ok(())
}
salidatornillos 180.00 tuercas 64.00 arandelas 11.25 TOTAL 255.25 salida.csv escrito
salida.csvnombre,precio,unidades clavos,0.1,500
Tres cosas que conviene notar. La primera es que los nombres de los campos del struct tienen que coincidir con los de la cabecera del CSV; si no coinciden, se renombran con #[serde(rename = "...")]. La segunda es que deserialize() entrega Result por fila, de ahí el fila? dentro del bucle: un CSV puede estar bien en la línea 1 y roto en la 500. Y la tercera es que el Writer escribe la cabecera solo, deducida de los nombres del struct.
0.1, no 0.10. Es lo mismo que pasa con JSON: un f64 guarda el número, no cuántos decimales se escribieron. Para la pantalla se arregla al formatear, con {:.2} como en el ejemplo. Si los dos decimales tienen que estar en el propio archivo, el precio no debería ser f64 sino un entero de céntimos o una cadena. Con dinero, además, los flotantes dan problemas de redondeo y lo correcto suele ser el entero.Variantes frecuentes
| Situación | Cómo |
|---|---|
| Separador de punto y coma | ReaderBuilder::new().delimiter(b';') |
| El archivo no tiene cabecera | .has_headers(false) |
| Leer sin declarar un struct | lector.records(), da StringRecord |
| Un campo puede venir vacío | declararlo Option<T> |
| Leer desde memoria, no de archivo | Reader::from_reader(texto.as_bytes()) |
| Escribir a la salida estándar | Writer::from_writer(io::stdout()) |
El código de esta página se compiló y ejecutó con rustc 1.96.1 y csv 1.4.0 antes de publicarla; las salidas y el archivo generado están copiados de la ejecución real. Documentación oficial: csv en docs.rs.