Los traits Read y Write son las abstracciones fundamentales para manejar flujos de datos (streams) en Rust. No representan un tipo de dato concreto como un archivo o un socket, sino que definen el comportamiento de cualquier tipo que pueda ser leído o escrito. Un tipo que implementa Read puede ser una fuente de bytes (entrada), y uno que implemente Write puede ser un destino de bytes (salida).
En la implementación de Read, el método principal es read(&mut self, buf: &mut [u8]) -> io::Result<usize>. Es crucial entender que read puede realizar una “lectura corta” (short read), es decir, devolver un número de bytes menor al tamaño del buffer proporcionado. Esto sucede porque el origen de los datos (como una conexión de red) puede tener menos datos disponibles de los que se solicitaron en ese instante. Si necesitas garantizar que se lean exactamente n bytes, debes utilizar read_exact, la cual devolverá un error si el flujo termina antes de completar el buffer. Para casos donde se requiera consumir todo el contenido disponible, se utilizan read_to_end (para Vec<u8>) o read_to_string (para String, validando que sea UTF-8).
En cuanto a Write, el método write(&mut self, buf: &[u8]) -> io::Result<usize> puede no escribir todo el buffer de una sola vez. Por ello, para asegurar la integridad de la escritura, se utiliza write_all. Si el destino utiliza un búfer intermedio (como BufWriter), es imperativo llamar a flush() para asegurar que todos los datos pendientes se escriban finalmente en el dispositivo de destino. Finalmente, para manipular flujos de datos, existen adaptadores como take(n), que permite limitar la lectura de un stream a un número específico de bytes, y std::io::copy, que transfiere datos directamente de un lector a un escritor de forma eficiente.
use std::io::{self, Read, Write, Cursor};
fn main() -> io::Result<()> {
// --- SECCIÓN DE ESCRITURA (Write) ---
// Usamos un Vec<u8> como destino, ya que implementa Write
let mut buffer_destino = Vec::new();
// write_all asegura que se escriban todos los bytes del slice
buffer_destino.write_all(b"Datos de prueba: ")?;
// La macro writeln! permite escribir con formato similar a println!
writeln!(buffer_destino, "Valor = {}", 42)?;
// --- SECCIÓN DE LECTURA (Read) ---
// Usamos Cursor para tratar el Vec como un objeto que implementa Read
let mut lector = Cursor::new(buffer_destino);
// 1. Lectura total a un String (verifica UTF-8 automáticamente)
let mut contenido_string = String::new();
lector.read_to_string(&mut contenido_string)?;
println!("Lectura completa (String): '{}'", contenido_string);
// Reiniciamos el puntero del cursor para la siguiente demostración
lector.set_position(0);
// 2. Uso de take(n) para limitar la lectura
// Creamos un adaptador que solo permitirá leer 15 bytes
let mut limitador = lector.take(15);
let mut buffer_limitado = [0u8; 15];
limitador.read_exact(&mut buffer_limitado)?;
println!("Lectura limitada (15 bytes): {:?}", buffer_limitado);
// 3. Lectura exacta de un fragmento específico
// Volvemos al inicio y leemos exactamente 10 bytes
lector.set_position(0);
let mut buffer_exacto = [0u8; 10];
lector.read_exact(&mut buffer_exacto)?;
println!("Lectura exacta (10 bytes): {:?}", buffer_exacto);
Ok(())
}
Explicación del Código
buffer_destino: Es unVec<u8>que actúa como nuestro buffer de salida. Al implementarWrite, permite recibir datos.write_all: Método deWriteutilizado sobrebuffer_destinopara asegurar que la secuencia de bytesb"Datos de prueba: "se escriba completamente.writeln!: Macro que escribe el formato"Valor = 42\n"enbuffer_destino, incluyendo un salto de línea.lector: Es una instancia deCursor<Vec<u8>>. El tipoCursorenvuelve una estructura que implementaRead(en este caso unVec) permitiendo navegar por él como si fuera un archivo.read_to_string: Método deReadque lee todo el contenido delectory lo decodifica encontenido_string, fallando si no es UTF-8 válido.take(15): Adaptador que envuelve alector. Crea un nuevo objeto que implementaReadpero limita la cantidad de bytes que puede extraer a 15, sin importar el tamaño real del origen.read_exact: Método deReadque intenta llenar el slicebuffer_limitadoybuffer_exactopor completo. Si el stream no tiene suficientes bytes para llenar el buffer, devuelve un error de tipoUnexpectedEof.set_position: Método propio deCursorpara mover el puntero de lectura/escritura a una posición específica, permitiendo reutilizar el mismo stream de datos.
N° 88