Introdução ao Módulo std::fs
O módulo std::fs (filesystem) é a porta de entrada para manipulação de arquivos em Rust. Ele fornece uma API segura e eficiente para criar, ler, escrever e manipular arquivos no sistema operacional. Diferentemente de linguagens como Python ou JavaScript, Rust força o desenvolvedor a lidar explicitamente com erros através do tipo Result, tornando o tratamento de exceções de I/O obrigatório e previsível.
Antes de começar, saiba que todo arquivo em Rust precisa ser aberto, lido/escrito e então fechado. O bom é que Rust fecha automaticamente os arquivos quando saem do escopo, graças ao trait Drop. Isso significa menos vazamento de recursos comparado a linguagens menos seguras.
Leitura de Arquivos
Leitura Simples com read_to_string
A forma mais direta de ler um arquivo inteiro é usar std::fs::read_to_string. Este método é ideal quando seu arquivo cabe na memória:
use std::fs;
fn main() -> std::io::Result<()> {
let conteudo = fs::read_to_string("dados.txt")?;
println!("Conteúdo do arquivo:\n{}", conteudo);
Ok(())
}
O operador ? desempacota o Result automaticamente. Se houver erro, a função retorna o erro imediatamente. Isso é muito mais limpo que verificar explicitamente match em cada operação.
Leitura em Chunks com BufReader
Para arquivos grandes, ler tudo de uma vez é ineficiente. Use BufReader para processar linha por linha:
use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() -> std::io::Result<()> {
let arquivo = File::open("grande_arquivo.txt")?;
let leitor = BufReader::new(arquivo);
for (numero, linha) in leitor.lines().enumerate() {
let linha = linha?;
println!("Linha {}: {}", numero + 1, linha);
}
Ok(())
}
Esta abordagem usa buffering interno, reduzindo chamadas ao sistema operacional. Cada linha é processada sob demanda, economizando memória mesmo com arquivos de gigabytes.
Leitura Binária
Para arquivos binários, use read ao invés de read_to_string:
use std::fs;
fn main() -> std::io::Result<()> {
let bytes = fs::read("imagem.png")?;
println!("Lidos {} bytes", bytes.len());
Ok(())
}
Este método retorna um Vec<u8>, permitindo processar dados binários como você precisar.
Escrita de Arquivos
Escrita Simples com write_all
Para escrever conteúdo de uma vez, use File::create combinado com write_all:
use std::fs::File;
use std::io::Write;
fn main() -> std::io::Result<()> {
let mut arquivo = File::create("saida.txt")?;
arquivo.write_all(b"Olá, Rust!\n")?;
arquivo.write_all(b"Segunda linha.\n")?;
Ok(())
}
Observe que arquivo deve ser mutável (mut). O método write_all garante que todos os bytes sejam escritos, retornando erro se alguma coisa falhar. Note que estamos usando literais de byte (b"string"), não strings Unicode diretas.
Escrita com BufWriter
Para múltiplas operações de escrita, BufWriter melhora a performance através de buffering:
use std::fs::File;
use std::io::{BufWriter, Write};
fn main() -> std::io::Result<()> {
let arquivo = File::create("log.txt")?;
let mut escritor = BufWriter::new(arquivo);
for i in 1..=1000 {
writeln!(escritor, "Entrada número: {}", i)?;
}
escritor.flush()?; // força a escrita do buffer
Ok(())
}
O writeln! é uma macro que escreve com quebra de linha. Note o flush() no final — sem isso, dados podem permanecer no buffer e não serem persistidos em disco imediatamente.
Append (Adicionar ao Final)
Para adicionar conteúdo ao final de um arquivo existente, use OpenOptions:
use std::fs::OpenOptions;
use std::io::Write;
fn main() -> std::io::Result<()> {
let mut arquivo = OpenOptions::new()
.append(true)
.open("log.txt")?;
writeln!(arquivo, "Nova entrada")?;
Ok(())
}
Se o arquivo não existir, OpenOptions::append falhará. Para criar se não existir, adicione .create(true).
Manipulação Avançada
Metadados e Verificações
Antes de processar um arquivo, você pode querer verificar seu tamanho ou existência:
use std::fs;
fn main() -> std::io::Result<()> {
let metadados = fs::metadata("arquivo.txt")?;
println!("Tamanho: {} bytes", metadados.len());
println!("É um arquivo? {}", metadados.is_file());
println!("É um diretório? {}", metadados.is_dir());
println!("Somente leitura? {}", metadados.permissions().readonly());
Ok(())
}
Este padrão é essencial para validar argumentos antes de operações custosas.
Cópia Eficiente de Arquivos
Rust oferece copy que delega ao sistema operacional quando possível:
use std::fs;
fn main() -> std::io::Result<()> {
let bytes_copiados = fs::copy("origem.txt", "destino.txt")?;
println!("Copiados {} bytes", bytes_copiados);
Ok(())
}
Esta função é mais eficiente que ler e escrever manualmente, especialmente para arquivos grandes.
Conclusão
Você aprendeu que o módulo std::fs de Rust combina segurança (através de Result obrigatório), eficiência (via buffering e gerenciamento automático de recursos) e simplicidade (com uma API intuitiva). Os padrões principais são: use read_to_string para leitura simples, BufReader para grandes arquivos, e BufWriter para múltiplas escritas. Sempre trate erros com o operador ? e lembre-se de que mut é necessário para escrita.
Na prática, a maioria dos programas Rust que lidam com I/O seguem esses padrões. Domine-os e você terá uma vantagem significativa em projetos reais.