Rust y archivos: mitos, miedos y enfoques de facto
¿Alguna vez te has preguntado: «¿Cómo se leen y escriben archivos correctamente en Rust moderno?». Si es así, no estás solo. En Stack Overflow, la pregunta «What's the de-facto way of reading and writing files in Rust 1.x?» ha acumulado miles de visitas y decenas de respuestas. Es un verdadero dolor para la comunidad, especialmente para quienes vienen de Python o JavaScript, donde open() y read() son la base, mientras que en Rust hay que lidiar con ownership, borrowing y montones de traits.
En este artículo, desglosaremos todas las formas principales de trabajar con archivos en Rust 1.x: desde «leer todo en un string» hasta la lectura eficiente línea por línea de grandes archivos de registro. Descubrirás qué enfoque se considera idiomático y cuál es mejor reservar para casos especiales. ¡Allá vamos!
1. std::fs: arsenal estándar
El módulo std::fs es tu primera parada. Proporciona funciones para leer, escribir, copiar y eliminar archivos. Rust 1.x ha estabilizado envoltorios convenientes que ocultan gran parte del trabajo de bajo nivel con llamadas al sistema.
Lectura completa de un archivo en un string
La forma más sencilla es usar std::fs::read_to_string. Abre el archivo, lo lee completamente en memoria y devuelve un Result<String>. Ideal para archivos de configuración pequeños o archivos de texto.
use std::fs;
fn main() -> std::io::Result<()> { let content = fs::read_to_string("hello.txt")?; println!("El archivo contiene:{}", content); Ok(())}Este método es el estándar de facto para casos simples. Es conciso, seguro y usa ? para el manejo de errores. La única desventaja es que todo el archivo se carga en memoria, por lo que no es adecuado para archivos de registro de gigabytes.
Lectura en un vector de bytes
Si trabajas con datos binarios (imágenes, archivos comprimidos), usa std::fs::read. Devuelve un Vec<u8>.
use std::fs;
fn main() -> std::io::Result<()> { let bytes = fs::read("image.png")?; println!("Leídos {} bytes", bytes.len()); Ok(())}Escritura en un archivo
Para escribir, usa std::fs::write. Toma una ruta y datos (un string o un arreglo de bytes) y crea/sobrescribe el archivo.
use std::fs;
fn main() -> std::io::Result<()> { fs::write("output.txt", "¡Hola, Rust!")?; Ok(())}Tan simple como dos y dos son cuatro. Pero si necesitas añadir al final del archivo en lugar de sobrescribir, tendrás que usar OpenOptions.
2. BufReader y BufWriter: cuando el tamaño importa
Para archivos grandes u operaciones frecuentes de entrada/salida, los envoltorios con búfer vienen al rescate. Reducen la cantidad de llamadas al sistema, lo cual es crítico para el rendimiento.
Lectura línea por línea con BufReader
Un ejemplo clásico es leer un archivo de registro grande línea por línea:
use std::fs::File;use std::io::{BufRead, BufReader};
fn main() -> std::io::Result<()> { let file = File::open("large_log.txt")?; let reader = BufReader::new(file);
for line in reader.lines() { let line = line?; println!("{}", line); } Ok(())}El método lines() devuelve un iterador de líneas, cada una de las cuales es un Result<String>. Observa el line? dentro del bucle: es el manejo de errores de lectura de cada línea.
Importante: lines() elimina los caracteres de nueva línea. Si necesitas conservarlos, usa read_until o lee el archivo como Vec<u8>.
Escritura con BufWriter
Al escribir grandes volúmenes de datos, el buffer también es útil:
use std::fs::File;use std::io::{BufWriter, Write};
fn main() -> std::io::Result<