Las operaciones de entrada/salida (I/O) son costosas porque cada llamada al sistema (syscall) requiere que la CPU cambie del modo usuario al modo kernel. Realizar múltiples lecturas o escrituras de pocos bytes de forma directa es ineficiente. Para mitigar esto, Rust proporciona mecanismos de buffering que agrupan estas operaciones en memoria antes de interactuar con el hardware.
BufReader<R> envuelve cualquier tipo que implemente el trait Read. Utiliza un buffer interno para leer grandes bloques de datos de una vez, permitiendo que las lecturas pequeñas se satisfagan desde la memoria RAM. Al implementar el trait BufRead, permite métodos optimizados como read_line (para leer hasta un carácter de nueva línea en un String) y lines() (un iterador que devuelve Result<String>), facilitando el procesamiento de archivos línea por línea.
BufWriter<W> funciona de forma inversa: acumula los datos escritos en un buffer interno y solo realiza la syscall de escritura cuando el buffer se llena o se vacía manualmente. Es crucial entender que, aunque BufWriter intenta realizar un flush automático cuando se libera de la memoria (drop), este proceso no puede devolver un error. Si la última escritura falla al cerrarse, el error se perderá silenciosamente. Por ello, siempre se debe llamar explícitamente a flush() antes de que el objeto salga de alcance para asegurar la integridad de los datos.
Para la comunicación con el usuario, std::io::stdin(), stdout() y stderr() proporcionan acceso a los flujos estándar. Por defecto, cada vez que se usa uno de estos flujos, se bloquea y se desbloquea el hilo para asegurar la seguridad en entornos multihilo. En bucles intensivos, esto genera un rendimiento pobre; para evitarlo, se debe usar .lock(), que devuelve un manejador bloqueado que permite realizar múltiples operaciones de forma mucho más rápida.
use std::io::{self, BufReader, BufWriter, BufRead, Write, Cursor};
fn main() -> io::Result<()> {
// 1. Uso de BufReader con Cursor (simulando un archivo en memoria)
let input_data = b"Línea de ejemplo 1\nLínea de ejemplo 2\nLínea 3";
let reader = BufReader::new(Cursor::new(input_data));
println!("--- Lectura con BufReader ---");
// lines() devuelve un iterador de Result<String>
for line_result in reader.lines() {
let line = line_result?; // Propaga errores de I/O
println!("Leído: {}", line);
}
// 2. Uso de BufWriter para escritura eficiente
let mut output_buffer = Vec::new();
{
// Envolvemos el buffer en un BufWriter
let mut writer = BufWriter::new(&mut output_buffer);
writer.write_all(b"Hola, Rust!\n")?;
writer.write_all(b"Escritura eficiente con buffer.\n")?;
// Es vital hacer flush manual para capturar errores antes de que el writer se dropee
writer.flush()?;
}
println!("\n--- Escritura con BufWriter ---");
println!("Buffer resultante: {}", String::from_utf8_lossy(&output_buffer));
// 3. Uso de stdin con bloqueo para rendimiento
// En un entorno real, esto leería la entrada del usuario
let stdin = io::stdin();
let mut stdin_handle = stdin.lock();
let mut input_buffer = String::new();
// Nota: En este ejemplo no se lee realmente de la terminal para evitar bloqueo infinito
println!("(Nota: stdin().lock() está listo para uso eficiente)");
// 4. Uso de stderr para mensajes de error
io::stderr().write_all(b"Mensaje de error en stderr\n")?;
Ok(())
}
Explicación del Código
reader: Es una instancia deBufReader<Cursor<[u8; 29]>>. Utiliza unCursorpara tratar un array de bytes como si fuera un flujo de lectura (Read).reader.lines(): Este método del traitBufReadcrea un iterador que procesa el contenido dereaderbuscando saltos de línea, devolviendo cada línea como unStringenvuelto en unResult.output_buffer: UnVec<u8>que actúa como destino para la escritura.writer: Es unBufWriterque envuelve una referencia mutua aoutput_buffer. Esto significa que los datos no se escriben en el vector inmediatamente, sino que se guardan en el buffer interno dewriter.writer.flush()?: Esta llamada es crítica. Obliga alBufWritera volcar todo su contenido interno haciaoutput_buffer. Al usar el operador?, si el proceso de volcado falla, el error se propagará.stdin.lock(): Obtiene unStdinLock. Al llamar astdin.lock(), evitamos que el sistema operativo tenga que adquirir y liberar el mutex de sincronización destdinen cada iteración, optimizando el rendimiento.stdin_handle: Es el manejador bloqueado que permite realizar operaciones de lectura de forma segura y rápida.io::stderr(): Se utiliza para escribir directamente en el flujo de error estándar, ideal para logs o mensajes que no deben mezclarse con la salida principal de datos.
N° 89