Read e Write Traits em Rust

Referência completa das traits Read e Write em Rust: métodos read, read_to_string, write_all, flush, chain e implementação para tipos customizados.

Read e Write Traits em Rust

As traits std::io::Read e std::io::Write são as abstrações centrais para I/O no Rust. Qualquer fonte de dados implementa Read (arquivos, sockets, buffers em memória), e qualquer destino implementa Write. Programar contra essas traits torna seu código genérico — a mesma função pode processar dados de um arquivo, da rede ou de um vetor de bytes.

Visão geral e tipos-chave

Trait Read

A trait Read define a interface para leitura de bytes. Seu único método obrigatório é read(), mas ela fornece vários métodos auxiliares com implementações padrão.

pub trait Read {
    // Método obrigatório
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;

    // Métodos com implementação padrão
    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize>;
    fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize>;
    fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()>;
    fn chain<R: Read>(self, next: R) -> Chain<Self, R>;
    fn take(self, limit: u64) -> Take<Self>;
    fn bytes(self) -> Bytes<Self>;
    // ...
}

Trait Write

A trait Write define a interface para escrita de bytes. Possui dois métodos obrigatórios: write() e flush().

pub trait Write {
    // Métodos obrigatórios
    fn write(&mut self, buf: &[u8]) -> io::Result<usize>;
    fn flush(&mut self) -> io::Result<()>;

    // Métodos com implementação padrão
    fn write_all(&mut self, buf: &[u8]) -> io::Result<()>;
    fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()>;
    // ...
}

Padrões comuns com código

Leitura básica com Read

use std::fs::File;
use std::io::{self, Read};

fn main() -> io::Result<()> {
    let mut arquivo = File::open("dados.txt")?;

    // read() — lê até encher o buffer, retorna quantos bytes leu
    let mut buffer = [0u8; 512];
    let n = arquivo.read(&mut buffer)?;
    println!("Lidos {} bytes: {:?}", n, &buffer[..n]);

    // read_to_string() — lê tudo como texto UTF-8
    let mut arquivo = File::open("texto.txt")?;
    let mut conteudo = String::new();
    let tamanho = arquivo.read_to_string(&mut conteudo)?;
    println!("Lidos {} bytes: {}", tamanho, &conteudo[..50.min(conteudo.len())]);

    // read_to_end() — lê tudo como bytes
    let mut arquivo = File::open("imagem.png")?;
    let mut bytes = Vec::new();
    arquivo.read_to_end(&mut bytes)?;
    println!("Arquivo tem {} bytes", bytes.len());

    Ok(())
}

Leitura exata com read_exact

O read_exact() garante que o buffer inteiro seja preenchido, ou retorna erro:

use std::fs::File;
use std::io::{self, Read};

fn ler_cabecalho(caminho: &str) -> io::Result<[u8; 8]> {
    let mut arquivo = File::open(caminho)?;
    let mut cabecalho = [0u8; 8];

    // Garante que exatamente 8 bytes sejam lidos
    // Retorna UnexpectedEof se o arquivo for menor
    arquivo.read_exact(&mut cabecalho)?;

    Ok(cabecalho)
}

fn main() -> io::Result<()> {
    match ler_cabecalho("arquivo.bin") {
        Ok(cab) => println!("Cabeçalho: {:02X?}", cab),
        Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
            eprintln!("Arquivo menor que 8 bytes");
        }
        Err(e) => return Err(e),
    }
    Ok(())
}

Escrita básica com Write

use std::fs::File;
use std::io::{self, Write};

fn main() -> io::Result<()> {
    let mut arquivo = File::create("saida.txt")?;

    // write() — pode escrever parcialmente, retorna quantos bytes escreveu
    let n = arquivo.write(b"Linha parcial possivel")?;
    println!("Escritos {} bytes", n);

    // write_all() — garante que TODOS os bytes sejam escritos
    arquivo.write_all(b"\nLinha completa garantida\n")?;

    // write! e writeln! — escrita formatada via write_fmt()
    let nome = "Rust Brasil";
    let versao = 2026;
    writeln!(arquivo, "Projeto: {} ({})", nome, versao)?;

    // flush() — força envio de dados bufferizados
    arquivo.flush()?;

    println!("Arquivo escrito com sucesso");
    Ok(())
}

Tabela de métodos

Métodos de Read

MétodoRetornoDescrição
read(&mut buf)io::Result<usize>Lê bytes no buffer; pode ler menos que o buffer
read_to_end(&mut vec)io::Result<usize>Lê todos os bytes até EOF em um Vec<u8>
read_to_string(&mut s)io::Result<usize>Lê tudo como UTF-8 em uma String
read_exact(&mut buf)io::Result<()>Lê exatamente buf.len() bytes ou falha
take(limit)Take<Self>Limita leitura a N bytes
chain(next)Chain<Self, R>Encadeia dois leitores sequencialmente
bytes()Bytes<Self>Iterador byte a byte
by_ref()&mut SelfReferência mutável para uso temporário

Métodos de Write

MétodoRetornoDescrição
write(&buf)io::Result<usize>Escreve bytes; pode escrever parcialmente
write_all(&buf)io::Result<()>Escreve todos os bytes ou falha
write_fmt(args)io::Result<()>Escrita formatada (usado por write!/writeln!)
flush()io::Result<()>Envia dados bufferizados ao destino
by_ref()&mut SelfReferência mutável para uso temporário

Exemplos práticos

Exemplo 1: Função genérica que aceita qualquer Read

use std::io::{self, BufRead, BufReader, Read};

/// Conta palavras em qualquer fonte que implemente Read
fn contar_palavras<R: Read>(fonte: R) -> io::Result<usize> {
    let leitor = BufReader::new(fonte);
    let mut total = 0;

    for linha in leitor.lines() {
        let linha = linha?;
        total += linha.split_whitespace().count();
    }

    Ok(total)
}

fn main() -> io::Result<()> {
    // Com arquivo
    let arquivo = std::fs::File::open("documento.txt")?;
    println!("Palavras no arquivo: {}", contar_palavras(arquivo)?);

    // Com buffer em memória (para testes)
    let texto = b"Rust e seguro e rapido.\nAprenda Rust hoje.";
    let cursor = io::Cursor::new(texto);
    println!("Palavras no texto: {}", contar_palavras(cursor)?);

    Ok(())
}

Exemplo 2: Implementando Read para um tipo customizado

use std::io::{self, Read};

/// Leitor que gera uma sequência numérica como texto
struct GeradorSequencia {
    atual: u64,
    limite: u64,
    buffer_interno: Vec<u8>,
    posicao: usize,
}

impl GeradorSequencia {
    fn new(limite: u64) -> Self {
        Self {
            atual: 0,
            limite,
            buffer_interno: Vec::new(),
            posicao: 0,
        }
    }
}

impl Read for GeradorSequencia {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        // Reabastecer buffer interno se necessário
        if self.posicao >= self.buffer_interno.len() {
            if self.atual >= self.limite {
                return Ok(0); // EOF
            }
            self.buffer_interno = format!("{}\n", self.atual).into_bytes();
            self.posicao = 0;
            self.atual += 1;
        }

        let disponivel = &self.buffer_interno[self.posicao..];
        let n = buf.len().min(disponivel.len());
        buf[..n].copy_from_slice(&disponivel[..n]);
        self.posicao += n;
        Ok(n)
    }
}

fn main() -> io::Result<()> {
    let mut gerador = GeradorSequencia::new(5);
    let mut saida = String::new();
    gerador.read_to_string(&mut saida)?;
    println!("Sequência gerada:\n{}", saida);
    // Saída: 0\n1\n2\n3\n4\n
    Ok(())
}

Exemplo 3: Implementando Write para um tipo customizado

use std::io::{self, Write};

/// Escritor que conta estatísticas enquanto escreve
struct EscritorEstatistico<W: Write> {
    interno: W,
    bytes_totais: usize,
    chamadas_write: usize,
    chamadas_flush: usize,
}

impl<W: Write> EscritorEstatistico<W> {
    fn new(interno: W) -> Self {
        Self {
            interno,
            bytes_totais: 0,
            chamadas_write: 0,
            chamadas_flush: 0,
        }
    }

    fn relatorio(&self) -> String {
        format!(
            "Bytes: {}, writes: {}, flushes: {}",
            self.bytes_totais, self.chamadas_write, self.chamadas_flush
        )
    }
}

impl<W: Write> Write for EscritorEstatistico<W> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let n = self.interno.write(buf)?;
        self.bytes_totais += n;
        self.chamadas_write += 1;
        Ok(n)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.chamadas_flush += 1;
        self.interno.flush()
    }
}

fn main() -> io::Result<()> {
    let destino = Vec::new();
    let mut escritor = EscritorEstatistico::new(destino);

    writeln!(escritor, "Primeira linha")?;
    writeln!(escritor, "Segunda linha com mais dados")?;
    escritor.write_all(b"Terceira linha\n")?;
    escritor.flush()?;

    println!("{}", escritor.relatorio());
    // Saída: Bytes: 60, writes: 3, flushes: 1
    Ok(())
}

Exemplo 4: Encadeando leitores com chain e limitando com take

use std::io::{self, Read};

fn main() -> io::Result<()> {
    let prefixo = io::Cursor::new(b"HEADER|" as &[u8]);
    let dados = io::Cursor::new(b"conteudo_real_dos_dados_do_sistema" as &[u8]);
    let sufixo = io::Cursor::new(b"|FOOTER" as &[u8]);

    // Encadear três fontes
    let combinado = prefixo.chain(dados).chain(sufixo);

    // Limitar a leitura total a 30 bytes
    let mut limitado = combinado.take(30);

    let mut resultado = String::new();
    limitado.read_to_string(&mut resultado)?;
    println!("Resultado (max 30 bytes): '{}'", resultado);
    println!("Tamanho: {} bytes", resultado.len());

    Ok(())
}

Exemplo 5: Transformar dados entre Read e Write

use std::io::{self, BufRead, BufReader, Write};

/// Converte texto para maiúsculas lendo de qualquer Read e escrevendo em qualquer Write
fn converter_maiusculas<R: io::Read, W: Write>(entrada: R, mut saida: W) -> io::Result<u64> {
    let leitor = BufReader::new(entrada);
    let mut bytes_escritos: u64 = 0;

    for linha in leitor.lines() {
        let linha_upper = linha?.to_uppercase();
        let dados = format!("{}\n", linha_upper);
        saida.write_all(dados.as_bytes())?;
        bytes_escritos += dados.len() as u64;
    }

    saida.flush()?;
    Ok(bytes_escritos)
}

fn main() -> io::Result<()> {
    let entrada = io::Cursor::new("olá mundo\nrust é incrível\n");
    let mut saida = Vec::new();

    let n = converter_maiusculas(entrada, &mut saida)?;
    println!("Convertidos {} bytes:", n);
    println!("{}", String::from_utf8_lossy(&saida));

    // Também funciona com arquivos:
    // let entrada = File::open("entrada.txt")?;
    // let saida = File::create("saida.txt")?;
    // converter_maiusculas(entrada, saida)?;

    Ok(())
}

Padrões de tratamento de erro para I/O

write() vs write_all()

Uma armadilha comum é usar write() esperando que todos os bytes sejam escritos. O write() pode escrever parcialmente — use write_all() para garantia:

use std::io::{self, Write};

fn escrita_segura<W: Write>(mut destino: W, dados: &[u8]) -> io::Result<()> {
    // ERRADO: write() pode não escrever tudo
    // let _ = destino.write(dados)?;

    // CORRETO: write_all() garante escrita completa
    destino.write_all(dados)?;
    destino.flush()?;
    Ok(())
}

Tratando read() retornando 0

Um retorno de 0 em read() significa fim do fluxo (EOF). Sempre trate esse caso:

use std::io::{self, Read};

fn ler_em_blocos<R: Read>(mut fonte: R) -> io::Result<Vec<u8>> {
    let mut resultado = Vec::new();
    let mut buffer = [0u8; 4096];

    loop {
        match fonte.read(&mut buffer) {
            Ok(0) => break,          // EOF — parar
            Ok(n) => resultado.extend_from_slice(&buffer[..n]),
            Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
            Err(e) => return Err(e),
        }
    }

    Ok(resultado)
}

Dicas de desempenho

  • Envolva Read com BufReader e Write com BufWriter para reduzir syscalls quando fizer muitas operações pequenas.
  • Use read_to_end com Vec::with_capacity se souber o tamanho aproximado dos dados para evitar realocações.
  • Prefira write_all a múltiplos write — evita a lógica de loop manual para escrita completa.
  • Use io::copy para transferências diretas — é otimizado internamente e evita alocações intermediárias.

Veja também