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 un Vec<u8> que actúa como nuestro buffer de salida. Al implementar Write, permite recibir datos.
  • write_all: Método de Write utilizado sobre buffer_destino para asegurar que la secuencia de bytes b"Datos de prueba: " se escriba completamente.
  • writeln!: Macro que escribe el formato "Valor = 42\n" en buffer_destino, incluyendo un salto de línea.
  • lector: Es una instancia de Cursor<Vec<u8>>. El tipo Cursor envuelve una estructura que implementa Read (en este caso un Vec) permitiendo navegar por él como si fuera un archivo.
  • read_to_string: Método de Read que lee todo el contenido de lector y lo decodifica en contenido_string, fallando si no es UTF-8 válido.
  • take(15): Adaptador que envuelve a lector. Crea un nuevo objeto que implementa Read pero limita la cantidad de bytes que puede extraer a 15, sin importar el tamaño real del origen.
  • read_exact: Método de Read que intenta llenar el slice buffer_limitado y buffer_exacto por completo. Si el stream no tiene suficientes bytes para llenar el buffer, devuelve un error de tipo UnexpectedEof.
  • set_position: Método propio de Cursor para mover el puntero de lectura/escritura a una posición específica, permitiendo reutilizar el mismo stream de datos.

88