Hay dos maneras de leer un archivo de texto en Rust, y la diferencia no es de estilo sino de memoria: una carga el archivo entero de golpe y la otra lo recorre línea a línea sin guardarlo todo. Con archivos pequeños da igual; con uno de varios gigabytes, no. Nada de esto necesita dependencias.
También te puede interesar
Los ejemplos leen este inventario.txt, el mismo que genera el ejemplo de escritura:
inventario.txttornillos;120 tuercas;80 arandelas;45
El archivo entero: fs::read_to_string
Una sola llamada abre el archivo, lo lee completo a una String y lo cierra:
use std::fs;
fn main() -> std::io::Result<()> {
let contenido = fs::read_to_string("inventario.txt")?;
print!("{contenido}");
println!("--- {} caracteres", contenido.len());
Ok(())
}
salidatornillos;120 tuercas;80 arandelas;45 --- 38 caracteres
Es la opción correcta casi siempre: un archivo de configuración, una plantilla, un JSON. El único inconveniente es que todo el contenido acaba en memoria, así que con archivos muy grandes conviene la siguiente forma.
Línea a línea: BufReader
BufReader lee por bloques y entrega las líneas de una en una, de modo que en memoria solo hay una cada vez:
use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() -> std::io::Result<()> {
let archivo = File::open("inventario.txt")?;
let lector = BufReader::new(archivo);
for (numero, linea) in lector.lines().enumerate() {
let linea = linea?;
let partes: Vec<&str> = linea.split(';').collect();
println!("{}: {} -> {}", numero + 1, partes[0], partes[1]);
}
Ok(())
}
salida1: tornillos -> 120 2: tuercas -> 80 3: arandelas -> 45
Hay un detalle que confunde al principio: lines() no devuelve cadenas sino Result<String>, porque la lectura puede fallar a mitad del recorrido. Por eso dentro del bucle aparece let linea = linea?;, que abre ese Result y reutiliza el nombre. lines() además quita el salto de línea final, así que no hay que recortarlo.
Cuando el archivo puede no existir
Si la ausencia del archivo es un caso previsto y no un error, conviene distinguirla del resto de fallos en vez de dejar que el programa termine:
use std::fs;
fn main() {
match fs::read_to_string("no_existe.txt") {
Ok(texto) => println!("{texto}"),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
println!("el archivo no existe, uso valores por defecto");
}
Err(e) => eprintln!("error al leer: {e}"),
}
}
salidael archivo no existe, uso valores por defecto
La segunda rama usa una guarda, esa condición if dentro del patrón, para separar «no está» de «está pero no se puede leer», que son problemas distintos: uno se resuelve con valores por defecto y el otro hay que reportarlo. Las guardas están explicadas en la lección de pattern matching.
Cuál usar
| Situación | Qué usar |
|---|---|
| Archivo pequeño o mediano, lo quiero entero | fs::read_to_string |
| Archivo grande, o proceso línea por línea | BufReader con lines() |
| Quiero las líneas en un vector | lector.lines().collect::<Result<Vec<_>, _>>()? |
| Son datos binarios, no texto | fs::read, que devuelve Vec<u8> |
read_to_string falla cuando el contenido no es texto UTF-8 válido, cosa frecuente con archivos antiguos en Latin-1 o generados en Windows. El error dice stream did not contain valid UTF-8. En ese caso hay que leer los bytes con fs::read y convertirlos con la codificación correcta, o usar String::from_utf8_lossy si aceptas que los caracteres no válidos se sustituyan.Todo el código de esta página se compiló y ejecutó con rustc 1.96.1 antes de publicarla; las salidas están copiadas de la ejecución real. Documentación oficial: std::fs::read_to_string y BufReader.