Módulo std::fs em Rust
O módulo std::fs fornece funções de alto nível para operações no sistema de arquivos: ler, escrever, copiar, renomear, remover arquivos e diretórios, consultar metadados e criar links simbólicos. Diferente do tipo File (que requer abrir e fechar handles manualmente), as funções de std::fs são operações completas em uma única chamada — ideais para tarefas simples e scripts.
Visão geral e tipos-chave
Funções de arquivo
fs::read_to_string(path)— lê arquivo inteiro comoString(UTF-8)fs::read(path)— lê arquivo inteiro comoVec<u8>(bytes)fs::write(path, conteudo)— escreve conteúdo em arquivo (cria ou sobrescreve)fs::copy(src, dst)— copia arquivo preservando permissõesfs::rename(src, dst)— move/renomeia arquivo ou diretóriofs::remove_file(path)— remove um arquivo
Funções de diretório
fs::create_dir(path)— cria um diretório (pai deve existir)fs::create_dir_all(path)— cria diretório e todos os pais necessáriosfs::remove_dir(path)— remove diretório vaziofs::remove_dir_all(path)— remove diretório e todo seu conteúdofs::read_dir(path)— retorna iterador sobre entradas do diretório
Funções de metadados
fs::metadata(path)— metadados seguindo symlinksfs::symlink_metadata(path)— metadados sem seguir symlinksfs::set_permissions(path, perm)— altera permissões
Padrões comuns com código
Leitura e escrita rápida
use std::fs;
use std::io;
fn main() -> io::Result<()> {
// Escrever texto em arquivo (cria ou sobrescreve)
fs::write("notas.txt", "Aprender Rust\nEstudar std::fs\n")?;
// Ler arquivo inteiro como String
let conteudo = fs::read_to_string("notas.txt")?;
println!("Notas ({} bytes):\n{}", conteudo.len(), conteudo);
// Ler como bytes (para binários)
let bytes = fs::read("notas.txt")?;
println!("Primeiros bytes: {:?}", &bytes[..10.min(bytes.len())]);
Ok(())
}
Gerenciar estrutura de diretórios
use std::fs;
use std::io;
fn criar_estrutura_projeto(nome: &str) -> io::Result<()> {
// create_dir_all cria toda a hierarquia de uma vez
fs::create_dir_all(format!("{}/src/models", nome))?;
fs::create_dir_all(format!("{}/src/controllers", nome))?;
fs::create_dir_all(format!("{}/src/views", nome))?;
fs::create_dir_all(format!("{}/tests", nome))?;
fs::create_dir_all(format!("{}/docs", nome))?;
// Criar arquivos iniciais
fs::write(
format!("{}/src/main.rs", nome),
"fn main() {\n println!(\"Hello, world!\");\n}\n",
)?;
fs::write(
format!("{}/Cargo.toml", nome),
format!(
"[package]\nname = \"{}\"\nversion = \"0.1.0\"\nedition = \"2024\"\n",
nome
),
)?;
println!("Projeto '{}' criado com sucesso", nome);
Ok(())
}
fn main() -> io::Result<()> {
criar_estrutura_projeto("meu_app")?;
Ok(())
}
Listar conteúdo de diretório
use std::fs;
use std::io;
fn listar_diretorio(caminho: &str) -> io::Result<()> {
println!("Conteúdo de '{}':", caminho);
let mut entradas: Vec<_> = fs::read_dir(caminho)?
.filter_map(|e| e.ok())
.collect();
// Ordenar por nome
entradas.sort_by_key(|e| e.file_name());
for entrada in &entradas {
let meta = entrada.metadata()?;
let tipo = if meta.is_dir() {
"DIR "
} else if meta.is_symlink() {
"LINK"
} else {
"FILE"
};
let tamanho = if meta.is_file() {
format!("{:>10} bytes", meta.len())
} else {
String::from(" -")
};
println!(
" [{}] {} {}",
tipo,
tamanho,
entrada.file_name().to_string_lossy()
);
}
println!("Total: {} entradas", entradas.len());
Ok(())
}
fn main() -> io::Result<()> {
listar_diretorio(".")?;
Ok(())
}
Tabela de funções principais
| Função | Retorno | Descrição |
|---|---|---|
fs::read_to_string(path) | io::Result<String> | Lê arquivo como texto UTF-8 |
fs::read(path) | io::Result<Vec<u8>> | Lê arquivo como bytes |
fs::write(path, conteudo) | io::Result<()> | Escreve bytes/string em arquivo |
fs::copy(src, dst) | io::Result<u64> | Copia arquivo, retorna bytes copiados |
fs::rename(src, dst) | io::Result<()> | Move/renomeia arquivo ou diretório |
fs::remove_file(path) | io::Result<()> | Remove arquivo |
fs::create_dir(path) | io::Result<()> | Cria diretório (pai deve existir) |
fs::create_dir_all(path) | io::Result<()> | Cria diretório com todos os pais |
fs::remove_dir(path) | io::Result<()> | Remove diretório vazio |
fs::remove_dir_all(path) | io::Result<()> | Remove diretório recursivamente |
fs::read_dir(path) | io::Result<ReadDir> | Iterador sobre entradas |
fs::metadata(path) | io::Result<Metadata> | Metadados (segue symlinks) |
fs::symlink_metadata(path) | io::Result<Metadata> | Metadados (não segue symlinks) |
fs::set_permissions(path, perm) | io::Result<()> | Altera permissões |
fs::canonicalize(path) | io::Result<PathBuf> | Caminho absoluto canônico |
fs::hard_link(src, dst) | io::Result<()> | Cria hard link |
fs::soft_link(src, dst) | io::Result<()> | Cria link simbólico (Unix) |
Exemplos práticos
Exemplo 1: Copiar e mover arquivos com verificação
use std::fs;
use std::io;
use std::path::Path;
fn copiar_seguro(origem: &str, destino: &str, sobrescrever: bool) -> io::Result<u64> {
let path_destino = Path::new(destino);
if path_destino.exists() && !sobrescrever {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
format!("Destino já existe: '{}'", destino),
));
}
// Garantir que o diretório pai existe
if let Some(pai) = path_destino.parent() {
fs::create_dir_all(pai)?;
}
let bytes = fs::copy(origem, destino)?;
println!("Copiado '{}' -> '{}' ({} bytes)", origem, destino, bytes);
Ok(bytes)
}
fn mover_arquivo(origem: &str, destino: &str) -> io::Result<()> {
// rename é atômico no mesmo filesystem
match fs::rename(origem, destino) {
Ok(()) => {
println!("Movido '{}' -> '{}'", origem, destino);
Ok(())
}
Err(e) if e.raw_os_error() == Some(18) => {
// EXDEV: cross-device — copiar e remover
fs::copy(origem, destino)?;
fs::remove_file(origem)?;
println!("Movido (cross-device) '{}' -> '{}'", origem, destino);
Ok(())
}
Err(e) => Err(e),
}
}
fn main() -> io::Result<()> {
fs::write("original.txt", "Dados importantes")?;
copiar_seguro("original.txt", "backup/original.txt", false)?;
mover_arquivo("original.txt", "arquivo/destino.txt")?;
Ok(())
}
Exemplo 2: Busca recursiva de arquivos
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
fn buscar_arquivos(
diretorio: &Path,
extensao: &str,
resultados: &mut Vec<PathBuf>,
) -> io::Result<()> {
if !diretorio.is_dir() {
return Ok(());
}
for entrada in fs::read_dir(diretorio)? {
let entrada = entrada?;
let caminho = entrada.path();
if caminho.is_dir() {
buscar_arquivos(&caminho, extensao, resultados)?;
} else if caminho.extension().and_then(|e| e.to_str()) == Some(extensao) {
resultados.push(caminho);
}
}
Ok(())
}
fn main() -> io::Result<()> {
let mut arquivos_rs = Vec::new();
buscar_arquivos(Path::new("."), "rs", &mut arquivos_rs)?;
println!("Arquivos .rs encontrados:");
for arquivo in &arquivos_rs {
let meta = fs::metadata(arquivo)?;
println!(" {} ({} bytes)", arquivo.display(), meta.len());
}
println!("Total: {} arquivos", arquivos_rs.len());
Ok(())
}
Exemplo 3: Limpeza de arquivos temporários
use std::fs;
use std::io;
use std::path::Path;
use std::time::{Duration, SystemTime};
fn limpar_antigos(diretorio: &str, max_idade: Duration) -> io::Result<(usize, u64)> {
let agora = SystemTime::now();
let mut removidos = 0usize;
let mut bytes_liberados = 0u64;
for entrada in fs::read_dir(diretorio)? {
let entrada = entrada?;
let meta = entrada.metadata()?;
if !meta.is_file() {
continue;
}
let modificado = meta.modified()?;
let idade = agora
.duration_since(modificado)
.unwrap_or(Duration::ZERO);
if idade > max_idade {
let tamanho = meta.len();
let nome = entrada.file_name();
match fs::remove_file(entrada.path()) {
Ok(()) => {
println!(" Removido: {} ({} bytes)", nome.to_string_lossy(), tamanho);
removidos += 1;
bytes_liberados += tamanho;
}
Err(e) => {
eprintln!(" Erro ao remover {}: {}", nome.to_string_lossy(), e);
}
}
}
}
Ok((removidos, bytes_liberados))
}
fn main() -> io::Result<()> {
let max_dias = Duration::from_secs(30 * 24 * 60 * 60); // 30 dias
let (removidos, bytes) = limpar_antigos("/tmp/meu_app", max_dias)?;
println!(
"Limpeza: {} arquivos removidos, {:.2} MB liberados",
removidos,
bytes as f64 / 1_048_576.0
);
Ok(())
}
Exemplo 4: Backup incremental de diretório
use std::fs;
use std::io;
use std::path::Path;
fn backup_diretorio(origem: &Path, destino: &Path) -> io::Result<(usize, usize)> {
fs::create_dir_all(destino)?;
let mut copiados = 0usize;
let mut ignorados = 0usize;
for entrada in fs::read_dir(origem)? {
let entrada = entrada?;
let tipo = entrada.file_type()?;
let nome = entrada.file_name();
let dest_path = destino.join(&nome);
if tipo.is_dir() {
let (c, i) = backup_diretorio(&entrada.path(), &dest_path)?;
copiados += c;
ignorados += i;
} else if tipo.is_file() {
let precisa_copiar = if dest_path.exists() {
let meta_orig = entrada.metadata()?;
let meta_dest = fs::metadata(&dest_path)?;
// Copiar se o original for mais recente ou tamanho diferente
meta_orig.len() != meta_dest.len()
|| meta_orig.modified()? > meta_dest.modified()?
} else {
true
};
if precisa_copiar {
fs::copy(entrada.path(), &dest_path)?;
copiados += 1;
} else {
ignorados += 1;
}
}
}
Ok((copiados, ignorados))
}
fn main() -> io::Result<()> {
let (copiados, ignorados) = backup_diretorio(
Path::new("projeto"),
Path::new("backup/projeto"),
)?;
println!("Backup: {} copiados, {} sem alteração", copiados, ignorados);
Ok(())
}
Exemplo 5: Calcular tamanho total de diretório
use std::fs;
use std::io;
use std::path::Path;
fn tamanho_diretorio(caminho: &Path) -> io::Result<u64> {
let mut total = 0u64;
if caminho.is_file() {
return Ok(fs::metadata(caminho)?.len());
}
for entrada in fs::read_dir(caminho)? {
let entrada = entrada?;
let meta = entrada.metadata()?;
if meta.is_file() {
total += meta.len();
} else if meta.is_dir() {
total += tamanho_diretorio(&entrada.path())?;
}
}
Ok(total)
}
fn formatar_tamanho(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} bytes", bytes)
}
}
fn main() -> io::Result<()> {
let caminhos = vec!["src", "target", "docs"];
for caminho in caminhos {
let path = Path::new(caminho);
if path.exists() {
let tamanho = tamanho_diretorio(path)?;
println!("{}: {}", caminho, formatar_tamanho(tamanho));
} else {
println!("{}: (não encontrado)", caminho);
}
}
Ok(())
}
Padrões de tratamento de erro para I/O
remove_dir_all com tratamento cuidadoso
use std::fs;
use std::io;
fn remover_seguro(caminho: &str) -> io::Result<()> {
match fs::remove_dir_all(caminho) {
Ok(()) => {
println!("Removido: '{}'", caminho);
Ok(())
}
Err(e) if e.kind() == io::ErrorKind::NotFound => {
println!("'{}' já não existe — nada a fazer", caminho);
Ok(()) // não é erro se já não existia
}
Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {
eprintln!("Sem permissão para remover: '{}'", caminho);
Err(e)
}
Err(e) => Err(e),
}
}
Verificação antes de operações destrutivas
use std::fs;
use std::io;
use std::path::Path;
fn substituir_arquivo(caminho: &str, novo_conteudo: &str) -> io::Result<()> {
let path = Path::new(caminho);
// Criar backup se existir
if path.exists() {
let backup = format!("{}.bak", caminho);
fs::copy(caminho, &backup)?;
println!("Backup criado: {}", backup);
}
fs::write(caminho, novo_conteudo)?;
println!("Arquivo atualizado: {}", caminho);
Ok(())
}
Dicas de desempenho
fs::read_to_stringefs::writesão adequados para arquivos pequenos (< 10 MB). Para arquivos maiores, useFilecomBufReader/BufWriter.read_diré lazy — as entradas são lidas sob demanda. Colete emVecapenas se precisar ordenar ou acessar múltiplas vezes.fs::copyé otimizada pelo sistema operacional em muitas plataformas (usasendfileno Linux, por exemplo).renameé atômico no mesmo filesystem — use para atualizações seguras de arquivos.
Veja também
- File e OpenOptions — controle fino sobre abertura de arquivos
- Path e PathBuf — manipulação de caminhos de arquivo
- BufReader e BufWriter — I/O bufferizado para performance
- Módulo std::io — traits e tipos de I/O
- Escrever em Arquivo — receita prática de escrita
- Documentação oficial:
std::fs