Rust e arquivos: mitos, medos e abordagens de facto
Você já se perguntou: "Qual é a maneira correta de ler e escrever arquivos no Rust moderno?". Se sim, você não está sozinho. No Stack Overflow, a pergunta "What's the de-facto way of reading and writing files in Rust 1.x?" acumulou milhares de visualizações e dezenas de respostas. Esta é uma verdadeira dor para a comunidade, especialmente para aqueles que vêm do Python ou JavaScript, onde open() e read() são o básico, e no Rust é preciso lidar com ownership, borrowing e uma série de traits.
Neste artigo, vamos detalhar todas as principais formas de trabalhar com arquivos em Rust 1.x: desde "apenas ler tudo para uma string" até a leitura eficiente linha por linha de grandes logs. Você aprenderá qual abordagem é considerada idiomática e qual é melhor guardar para casos especiais. Vamos lá!
1. std::fs: arsenal padrão
O módulo std::fs é sua primeira parada. Ele fornece funções para ler, escrever, copiar e excluir arquivos. O Rust 1.x estabilizou wrappers convenientes que ocultam a maior parte do trabalho de baixo nível com chamadas de sistema.
Lendo o arquivo inteiro para uma string
A maneira mais simples é usar std::fs::read_to_string. Ele abre o arquivo, lê todo o seu conteúdo para a memória e retorna um Result<String>. Ideal para pequenos arquivos de configuração ou arquivos de texto.
use std::fs;
fn main() -> std::io::Result<()> { let content = fs::read_to_string("hello.txt")?; println!("O arquivo contém:{}", content); Ok(())}Esta forma é o padrão de facto para casos simples. É concisa, segura e usa ? para tratamento de erros. A única desvantagem é que o arquivo inteiro é carregado na memória, portanto, não é adequado para logs de gigabytes.
Lendo para um vetor de bytes
Se você estiver trabalhando com dados binários (imagens, arquivos), use std::fs::read. Ele retorna um Vec<u8>.
use std::fs;
fn main() -> std::io::Result<()> { let bytes = fs::read("image.png")?; println!("Lidos {} bytes", bytes.len()); Ok(())}Escrevendo em um arquivo
Para escrever, use std::fs::write. Ele aceita um caminho e dados (string ou array de bytes) e cria/substitui o arquivo.
use std::fs;
fn main() -> std::io::Result<()> { fs::write("output.txt", "Olá, Rust!")?; Ok(())}Simples como dois mais dois. Mas se você precisar anexar ao final do arquivo, em vez de sobrescrevê-lo, terá que usar OpenOptions.
2. BufReader e BufWriter: quando o tamanho importa
Para arquivos grandes ou operações frequentes de E/S, os wrappers com buffer vêm em socorro. Eles reduzem o número de chamadas de sistema, o que é crítico para o desempenho.
Leitura linha por linha com BufReader
O exemplo clássico é ler um arquivo de log grande linha por linha:
use std::fs::File;use std::io::{BufRead, BufReader};
fn main() -> std::io::Result<()> { let file = File::open("large_log.txt")?; let reader = BufReader::new(file);
for line in reader.lines() { let line = line?; println!("{}", line); } Ok(())}O método lines() retorna um iterador de linhas, cada uma sendo um Result<String>. Observe o line? dentro do loop — é o tratamento de erros de leitura de cada linha.
Importante: lines() remove os caracteres de nova linha. Se você precisar mantê-los, use read_until ou leia o arquivo como Vec<u8>.
Escrevendo com BufWriter
Ao escrever grandes volumes de dados, o buffer também é útil:
use std::fs::File;use std::io::{BufWriter, Write};
fn main() -> std::io::Result<