File e OpenOptions em Rust

Referência completa de File e OpenOptions em Rust: abrir, criar, ler, escrever, append, truncate, metadata, permissões e seek com exemplos.

File e OpenOptions em Rust

O tipo std::fs::File representa um arquivo aberto no sistema operacional. Ele implementa as traits Read, Write e Seek, permitindo leitura, escrita e navegação posicional. Para controle fino sobre como um arquivo é aberto (leitura, escrita, append, truncamento, criação), use std::fs::OpenOptions, que fornece um builder configurável.

Visão geral e tipos-chave

File

File é um handle para um arquivo aberto. Quando o File sai de escopo, o arquivo é fechado automaticamente (via Drop). Os dois métodos estáticos mais usados são:

  • File::open(path) — abre para leitura (equivale a OpenOptions::new().read(true).open(path))
  • File::create(path) — cria ou trunca para escrita (equivale a OpenOptions::new().write(true).create(true).truncate(true).open(path))

OpenOptions

OpenOptions é um builder que configura como o arquivo será aberto:

  • read(true) — permite leitura
  • write(true) — permite escrita
  • append(true) — posiciona no final para escrita (implica write)
  • truncate(true) — limpa o conteúdo ao abrir (requer write)
  • create(true) — cria se não existir (requer write ou append)
  • create_new(true) — falha se o arquivo já existir (garante criação exclusiva)

Metadata e Permissions

  • Metadata — informações sobre o arquivo: tamanho, tipo, timestamps, permissões.
  • Permissions — permite verificar e alterar permissões de leitura/escrita.

Padrões comuns com código

Abrir e ler arquivo

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

fn main() -> io::Result<()> {
    // Abrir para leitura
    let mut arquivo = File::open("config.toml")?;
    let mut conteudo = String::new();
    arquivo.read_to_string(&mut conteudo)?;
    println!("Configuração ({} bytes):\n{}", conteudo.len(), conteudo);
    Ok(())
}

Criar e escrever arquivo

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

fn main() -> io::Result<()> {
    // File::create trunca se existir, cria se não existir
    let mut arquivo = File::create("resultado.txt")?;
    writeln!(arquivo, "Relatório gerado em 2026-02-23")?;
    writeln!(arquivo, "Total de registros: {}", 42)?;
    arquivo.flush()?;
    Ok(())
}

Adicionar conteúdo com append

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

fn adicionar_log(caminho: &str, mensagem: &str) -> io::Result<()> {
    let mut arquivo = OpenOptions::new()
        .append(true)
        .create(true)
        .open(caminho)?;

    writeln!(arquivo, "[{}] {}", chrono_simples(), mensagem)?;
    Ok(())
}

fn chrono_simples() -> String {
    // Simulação simples de timestamp
    "2026-02-23 14:30:00".to_string()
}

fn main() -> io::Result<()> {
    adicionar_log("app.log", "Servidor iniciado")?;
    adicionar_log("app.log", "Conexão recebida de 192.168.1.10")?;
    adicionar_log("app.log", "Processamento concluído")?;
    println!("Logs adicionados com sucesso");
    Ok(())
}

Uso avançado de OpenOptions

use std::fs::OpenOptions;
use std::io::{self, Read, Write, Seek, SeekFrom};

fn main() -> io::Result<()> {
    // Abrir para leitura E escrita
    let mut arquivo = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("dados.bin")?;

    // Escrever dados iniciais
    arquivo.write_all(b"AAAAAAAAAA")?;

    // Voltar ao início
    arquivo.seek(SeekFrom::Start(0))?;

    // Ler o que foi escrito
    let mut conteudo = String::new();
    arquivo.read_to_string(&mut conteudo)?;
    println!("Conteúdo: {}", conteudo);

    // Sobrescrever parcialmente (posição 3)
    arquivo.seek(SeekFrom::Start(3))?;
    arquivo.write_all(b"BBB")?;

    // Ler tudo novamente
    arquivo.seek(SeekFrom::Start(0))?;
    conteudo.clear();
    arquivo.read_to_string(&mut conteudo)?;
    println!("Após edição: {}", conteudo); // AAABBBAAAA

    Ok(())
}

Tabela de métodos e funções

Métodos de File

MétodoDescrição
File::open(path)Abre para leitura
File::create(path)Cria/trunca para escrita
File::create_new(path)Cria novo arquivo, falha se existir
metadata()Retorna metadados (tamanho, permissões, etc.)
set_permissions(perm)Altera permissões do arquivo
set_len(size)Define o tamanho do arquivo (trunca ou estende)
sync_all()Sincroniza dados e metadados com o disco
sync_data()Sincroniza apenas dados com o disco
try_clone()Cria handle duplicado para o mesmo arquivo
set_modified(time)Define timestamp de modificação

Métodos de OpenOptions

MétodoDescrição
OpenOptions::new()Cria builder com todas opções desativadas
read(bool)Habilita leitura
write(bool)Habilita escrita
append(bool)Escrita no final (implica write)
truncate(bool)Limpa conteúdo ao abrir
create(bool)Cria se não existir
create_new(bool)Falha se já existir
open(path)Abre o arquivo com as opções configuradas

Métodos de Seek

MétodoDescrição
seek(SeekFrom::Start(n))Posiciona em N bytes do início
seek(SeekFrom::End(n))Posiciona em N bytes do final
seek(SeekFrom::Current(n))Move N bytes da posição atual
rewind()Volta ao início (equivale a seek(Start(0)))
stream_position()Retorna a posição atual

Exemplos práticos

Exemplo 1: Criação exclusiva de arquivo (evitar sobrescrita)

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

fn criar_arquivo_seguro(caminho: &str, conteudo: &str) -> io::Result<()> {
    // create_new() falha se o arquivo já existir
    match File::create_new(caminho) {
        Ok(mut arquivo) => {
            arquivo.write_all(conteudo.as_bytes())?;
            println!("Arquivo '{}' criado com sucesso", caminho);
            Ok(())
        }
        Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
            eprintln!("Arquivo '{}' já existe — não sobrescrevendo", caminho);
            Err(e)
        }
        Err(e) => Err(e),
    }
}

fn main() -> io::Result<()> {
    criar_arquivo_seguro("relatorio_2026.txt", "Dados do relatório...")?;
    Ok(())
}

Exemplo 2: Arquivo de configuração com leitura/escrita

use std::fs::OpenOptions;
use std::io::{self, BufRead, BufReader, Seek, SeekFrom, Write};

fn atualizar_config(caminho: &str, chave: &str, novo_valor: &str) -> io::Result<bool> {
    let mut arquivo = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(caminho)?;

    let leitor = BufReader::new(&arquivo);
    let mut linhas: Vec<String> = Vec::new();
    let mut encontrou = false;

    for resultado in leitor.lines() {
        let linha = resultado?;
        if linha.starts_with(&format!("{}=", chave)) || linha.starts_with(&format!("{} =", chave)) {
            linhas.push(format!("{} = {}", chave, novo_valor));
            encontrou = true;
        } else {
            linhas.push(linha);
        }
    }

    if !encontrou {
        linhas.push(format!("{} = {}", chave, novo_valor));
    }

    // Reescrever o arquivo
    arquivo.seek(SeekFrom::Start(0))?;
    arquivo.set_len(0)?; // truncar
    for linha in &linhas {
        writeln!(arquivo, "{}", linha)?;
    }

    arquivo.sync_all()?;
    Ok(encontrou)
}

fn main() -> io::Result<()> {
    let atualizado = atualizar_config("app.conf", "porta", "8080")?;
    if atualizado {
        println!("Configuração atualizada");
    } else {
        println!("Nova configuração adicionada");
    }
    Ok(())
}

Exemplo 3: Consultar metadados e permissões

use std::fs;
use std::io;

fn exibir_info_arquivo(caminho: &str) -> io::Result<()> {
    let metadata = fs::metadata(caminho)?;

    println!("Arquivo: {}", caminho);
    println!("  Tamanho: {} bytes", metadata.len());
    println!("  É arquivo: {}", metadata.is_file());
    println!("  É diretório: {}", metadata.is_dir());
    println!("  É symlink: {}", metadata.is_symlink());
    println!("  Somente leitura: {}", metadata.permissions().readonly());

    if let Ok(modificado) = metadata.modified() {
        println!("  Modificado: {:?}", modificado);
    }

    if let Ok(criado) = metadata.created() {
        println!("  Criado: {:?}", criado);
    }

    Ok(())
}

fn tornar_somente_leitura(caminho: &str) -> io::Result<()> {
    let metadata = fs::metadata(caminho)?;
    let mut permissoes = metadata.permissions();
    permissoes.set_readonly(true);
    fs::set_permissions(caminho, permissoes)?;
    println!("'{}' agora é somente leitura", caminho);
    Ok(())
}

fn main() -> io::Result<()> {
    exibir_info_arquivo("Cargo.toml")?;
    Ok(())
}

Exemplo 4: Seek para edição em arquivos binários

use std::fs::OpenOptions;
use std::io::{self, Read, Seek, SeekFrom, Write};

/// Lê e modifica um campo em posição fixa de um arquivo binário
fn editar_campo_binario(
    caminho: &str,
    offset: u64,
    novo_valor: &[u8],
) -> io::Result<Vec<u8>> {
    let mut arquivo = OpenOptions::new()
        .read(true)
        .write(true)
        .open(caminho)?;

    // Ler valor antigo
    arquivo.seek(SeekFrom::Start(offset))?;
    let mut valor_antigo = vec![0u8; novo_valor.len()];
    arquivo.read_exact(&mut valor_antigo)?;

    // Escrever novo valor
    arquivo.seek(SeekFrom::Start(offset))?;
    arquivo.write_all(novo_valor)?;
    arquivo.sync_data()?;

    println!(
        "Offset {}: {:02X?} -> {:02X?}",
        offset, valor_antigo, novo_valor
    );

    Ok(valor_antigo)
}

fn main() -> io::Result<()> {
    // Criar arquivo de exemplo
    let mut f = std::fs::File::create("teste.bin")?;
    f.write_all(&[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07])?;
    drop(f);

    // Editar bytes na posição 2
    let antigo = editar_campo_binario("teste.bin", 2, &[0xFF, 0xFE])?;
    println!("Valor anterior: {:02X?}", antigo);

    Ok(())
}

Exemplo 5: sync_all para garantia de persistência

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

/// Escrita durável — garante que dados estejam no disco físico
fn escrever_duravel(caminho: &str, dados: &[u8]) -> io::Result<()> {
    let mut arquivo = File::create(caminho)?;
    arquivo.write_all(dados)?;

    // sync_all() garante que dados E metadados estejam no disco
    // Importante para bancos de dados, logs de transação, etc.
    arquivo.sync_all()?;

    Ok(())
}

/// Escrita atômica via arquivo temporário + rename
fn escrever_atomico(caminho: &str, dados: &[u8]) -> io::Result<()> {
    let caminho_temp = format!("{}.tmp", caminho);

    // 1. Escrever no arquivo temporário
    let mut temp = File::create(&caminho_temp)?;
    temp.write_all(dados)?;
    temp.sync_all()?;
    drop(temp);

    // 2. Renomear atomicamente (no mesmo filesystem)
    std::fs::rename(&caminho_temp, caminho)?;

    Ok(())
}

fn main() -> io::Result<()> {
    escrever_duravel("dados_importantes.dat", b"dados criticos")?;
    escrever_atomico("config.json", b"{\"chave\": \"valor\"}")?;
    println!("Escritas duráveis concluídas");
    Ok(())
}

Padrões de tratamento de erro para I/O

Erros comuns ao trabalhar com arquivos

use std::fs::File;
use std::io;

fn abrir_com_diagnostico(caminho: &str) -> io::Result<File> {
    File::open(caminho).map_err(|e| {
        match e.kind() {
            io::ErrorKind::NotFound => {
                eprintln!("Arquivo não encontrado: '{}'", caminho);
                eprintln!("Verifique o caminho e tente novamente.");
            }
            io::ErrorKind::PermissionDenied => {
                eprintln!("Sem permissão para abrir: '{}'", caminho);
                eprintln!("Verifique as permissões do arquivo.");
            }
            _ => {
                eprintln!("Erro inesperado ao abrir '{}': {}", caminho, e);
            }
        }
        e
    })
}

Padrão try_clone para leitura e escrita simultâneas

use std::fs::OpenOptions;
use std::io::{self, BufRead, BufReader, Write, Seek, SeekFrom};

fn ler_e_registrar(caminho_dados: &str, caminho_log: &str) -> io::Result<()> {
    let arquivo = OpenOptions::new().read(true).open(caminho_dados)?;

    // Clonar handle para usar em dois contextos
    let clone = arquivo.try_clone()?;
    let leitor = BufReader::new(arquivo);
    let mut log = std::fs::File::create(caminho_log)?;

    for (i, linha) in leitor.lines().enumerate() {
        let linha = linha?;
        writeln!(log, "Linha {}: {} chars", i + 1, linha.len())?;
    }

    Ok(())
}

Dicas de desempenho

  • Use BufReader e BufWriter ao fazer múltiplas operações pequenas sobre um File. A diferença pode ser de 10x a 100x em performance.
  • Use sync_data() em vez de sync_all() quando não precisar sincronizar metadados — é mais rápido em alguns sistemas de arquivos.
  • Pré-aloque com set_len() quando souber o tamanho final do arquivo para evitar fragmentação.
  • Escrita atômica via rename é mais segura que sobrescrever diretamente — evita corrupção em caso de falha de energia.

Veja também