Rust

Programação de Sistemas — Arquivos, Processos e Sinais Já leu

16 min de leitura

Programação de Sistemas — Arquivos, Processos e Sinais
Rust nasceu como linguagem de sistemas. Não é por acaso que ela substituiu C++ em partes críticas do kernel Linux, no Firefox, e em sistemas embarcados de segurança crítica. Neste artigo descemos ao nível do sistema oper

 

Rust nasceu como linguagem de sistemas. Não é por acaso que ela substituiu C++ em partes críticas do kernel Linux, no Firefox, e em sistemas embarcados de segurança crítica. Neste artigo descemos ao nível do sistema operacional: manipulação de arquivos com controle total, criação e gerenciamento de processos filhos, e tratamento de sinais Unix.

Este é território onde erros custam caro — corrupção de dados, processos zumbis, comportamento indefinido. Rust oferece as mesmas primitivas de baixo nível que C, mas com as garantias que tornaram a linguagem famosa.


Arquivos — além do básico

Vimos std::fs::read_to_string e std::fs::write nos artigos anteriores. Mas para controle real sobre arquivos, precisamos de std::fs::File e std::io:

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

fn demonstrar_operacoes_arquivo() -> io::Result<()> {
    // Criar arquivo para escrita
    let mut arquivo = File::create("dados.txt")?;
    arquivo.write_all(b"Linha 1
Linha 2
Linha 3
")?;

    // Abrir para leitura
    let arquivo = File::open("dados.txt")?;
    let leitor = BufReader::new(arquivo);

    println!("Conteúdo linha a linha:");
    for (i, linha) in leitor.lines().enumerate() {
        println!("  {}: {}", i + 1, linha?);
    }

    // Abrir com controle total sobre flags
    let mut arquivo = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)      // criar se não existir
        .append(false)     // não concatenar — sobrescrever
        .open("log.txt")?;

    writeln!(arquivo, "Entrada de log: {}", chrono_simples())?;

    // Seek — posicionar cursor no arquivo
    arquivo.seek(SeekFrom::Start(0))?;
    let mut conteudo = String::new();
    arquivo.read_to_string(&mut conteudo)?;
    println!("Log completo:
{conteudo}");

    // Seek do final do arquivo
    let tamanho = arquivo.seek(SeekFrom::End(0))?;
    println!("Tamanho: {tamanho} bytes");

    Ok(())
}

fn chrono_simples() -> String {
    use std::time::{SystemTime, UNIX_EPOCH};
    let secs = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs();
    format!("Unix timestamp: {secs}")
}

fn main() -> io::Result<()> {
    demonstrar_operacoes_arquivo()
}

Escrita eficiente com BufWriter

Escrever byte a byte em um arquivo é extremamente lento — cada write pode resultar em uma syscall. BufWriter acumula dados em memória e faz escritas em lotes:

use std::fs::File;
use std::io::{BufWriter, Write};
use std::time::Instant;

fn escrever_sem_buffer(n: usize) -> std::io::Result<()> {
    let mut f = File::create("sem_buffer.txt")?;
    for i in 0..n {
        writeln!(f, "linha {i}")?;
    }
    Ok(())
}

fn escrever_com_buffer(n: usize) -> std::io::Result<()> {
    let f = File::create("com_buffer.txt")?;
    let mut f = BufWriter::new(f);
    for i in 0..n {
        writeln!(f, "linha {i}")?;
    }
    f.flush()?; // garante que o buffer seja escrito
    Ok(())
}

fn main() -> std::io::Result<()> {
    let n = 100_000;

    let inicio = Instant::now();
    escrever_sem_buffer(n)?;
    println!("Sem buffer: {:?}", inicio.elapsed());

    let inicio = Instant::now();
    escrever_com_buffer(n)?;
    println!("Com buffer: {:?}", inicio.elapsed());

    // Saída típica:
    // Sem buffer: 1.2s
    // Com buffer: 45ms

    Ok(())
}

Leitura de arquivos grandes linha a linha

Para arquivos que não cabem na memória, processe linha a linha:

use std::fs::File;
use std::io::{BufRead, BufReader};

fn processar_csv_grande(caminho: &str) -> std::io::Result<EstatisticasCSV> {
    let arquivo = File::open(caminho)?;
    let leitor = BufReader::new(arquivo);
    let mut linhas = leitor.lines();

    // Lê cabeçalho
    let cabecalho = linhas.next()
        .ok_or(std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            "Arquivo vazio"
        ))??;

    let colunas: Vec<&str> = cabecalho.split(',').collect();
    println!("Colunas: {:?}", colunas);

    let mut total_linhas = 0usize;
    let mut total_erros = 0usize;
    let mut soma_valores = 0.0f64;

    for resultado in linhas {
        let linha = resultado?;
        total_linhas += 1;

        let campos: Vec<&str> = linha.split(',').collect();
        if campos.len() < 2 {
            total_erros += 1;
            continue;
        }

        if let Ok(valor) = campos[1].trim().parse::<f64>() {
            soma_valores += valor;
        } else {
            total_erros += 1;
        }
    }

    Ok(EstatisticasCSV {
        total_linhas,
        total_erros,
        media: if total_linhas > 0 {
            soma_valores / total_linhas as f64
        } else {
            0.0
        },
    })
}

struct EstatisticasCSV {
    total_linhas: usize,
    total_erros: usize,
    media: f64,
}

fn main() -> std::io::Result<()> {
    // Cria arquivo de teste
    {
        use std::io::Write;
        let mut f = std::fs::File::create("dados.csv")?;
        writeln!(f, "nome,valor")?;
        writeln!(f, "alpha,10.5")?;
        writeln!(f, "beta,20.3")?;
        writeln!(f, "gamma,invalido")?;
        writeln!(f, "delta,15.8")?;
    }

    match processar_csv_grande("dados.csv") {
        Ok(stats) => {
            println!("Linhas processadas : {}", stats.total_linhas);
            println!("Erros encontrados  : {}", stats.total_erros);
            println!("Média dos valores  : {:.2}", stats.media);
        }
        Err(e) => eprintln!("Erro: {e}"),
    }

    Ok(())
}

Metadados e sistema de arquivos

use std::fs;
use std::path::Path;

fn inspecionar_caminho(caminho: &str) -> std::io::Result<()> {
    let path = Path::new(caminho);

    if !path.exists() {
        println!("{caminho}: não existe");
        return Ok(());
    }

    let metadata = fs::metadata(path)?;

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

    if let Ok(modificado) = metadata.modified() {
        use std::time::SystemTime;
        let duracao = SystemTime::now()
            .duration_since(modificado)
            .unwrap_or_default();
        println!("  Modificado há: {}s", duracao.as_secs());
    }

    Ok(())
}

fn listar_diretorio_recursivo(dir: &Path, prefixo: &str) -> std::io::Result<()> {
    if !dir.is_dir() {
        return Ok(());
    }

    let mut entradas: Vec<_> = fs::read_dir(dir)?
        .filter_map(|e| e.ok())
        .collect();

    // Ordena: diretórios primeiro, depois arquivos, ambos por nome
    entradas.sort_by(|a, b| {
        let a_dir = a.path().is_dir();
        let b_dir = b.path().is_dir();
        match (a_dir, b_dir) {
            (true, false) => std::cmp::Ordering::Less,
            (false, true) => std::cmp::Ordering::Greater,
            _ => a.file_name().cmp(&b.file_name()),
        }
    });

    for (i, entrada) in entradas.iter().enumerate() {
        let e_ultimo = i == entradas.len() - 1;
        let conector = if e_ultimo { "└── " } else { "├── " };
        let extensao = if e_ultimo { "    " } else { "│   " };

        let nome = entrada.file_name();
        let nome = nome.to_string_lossy();
        let path = entrada.path();

        if path.is_dir() {
            println!("{prefixo}{conector}{nome}/");
            let novo_prefixo = format!("{prefixo}{extensao}");
            listar_diretorio_recursivo(&path, &novo_prefixo)?;
        } else {
            let meta = fs::metadata(&path)?;
            println!("{prefixo}{conector}{nome} ({} bytes)", meta.len());
        }
    }

    Ok(())
}

fn main() -> std::io::Result<()> {
    // Cria estrutura de diretórios para demonstração
    fs::create_dir_all("projeto/src")?;
    fs::create_dir_all("projeto/tests")?;
    fs::write("projeto/Cargo.toml", "[package]
name = "demo"")?;
    fs::write("projeto/src/main.rs", "fn main() {}")?;
    fs::write("projeto/src/lib.rs", "pub mod utils;")?;
    fs::write("projeto/tests/integracao.rs", "#[test] fn ok() {}")?;

    println!("projeto/");
    listar_diretorio_recursivo(Path::new("projeto"), "")?;

    inspecionar_caminho("projeto/Cargo.toml")?;

    // Limpeza
    fs::remove_dir_all("projeto")?;

    Ok(())
}

Saída:

projeto/
├── src/
│   ├── lib.rs (18 bytes)
│   └── main.rs (14 bytes)
├── tests/
│   └── integracao.rs (22 bytes)
└── Cargo.toml (27 bytes)

── projeto/Cargo.toml ──
  É arquivo    : true
  É diretório  : false
  Tamanho      : 27 bytes
  Somente leit.: false
  Modificado há: 0s

Processos — criando e gerenciando filhos

std::process::Command permite criar subprocessos com controle total sobre stdin, stdout e stderr:

use std::process::{Command, Stdio};
use std::io::Write;

fn executar_comando_simples() {
    // Executa e espera terminar
    let saida = Command::new("echo")
        .arg("Olá do processo filho!")
        .output()
        .expect("Falha ao executar echo");

    println!("Status : {}", saida.status);
    println!("Stdout : {}", String::from_utf8_lossy(&saida.stdout));
    println!("Stderr : {}", String::from_utf8_lossy(&saida.stderr));
}

fn pipeline_de_comandos() -> std::io::Result<()> {
    // Equivalente a: echo "hello world" | tr '[:lower:]' '[:upper:]'
    let echo = Command::new("echo")
        .arg("hello world from rust")
        .stdout(Stdio::piped())
        .spawn()?;

    let tr = Command::new("tr")
        .arg("[:lower:]")
        .arg("[:upper:]")
        .stdin(echo.stdout.unwrap())
        .output()?;

    println!("Pipeline: {}", String::from_utf8_lossy(&tr.stdout));
    Ok(())
}

fn enviar_para_stdin() -> std::io::Result<()> {
    // Envia dados para o stdin de um processo
    let mut filho = Command::new("cat")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()?;

    // Escreve no stdin do filho
    if let Some(ref mut stdin) = filho.stdin {
        writeln!(stdin, "Linha enviada ao stdin do processo filho")?;
        writeln!(stdin, "Segunda linha")?;
    }

    // Fecha stdin — o processo filho saberá que acabou
    drop(filho.stdin.take());

    let saida = filho.wait_with_output()?;
    println!("Cat retornou: {}", String::from_utf8_lossy(&saida.stdout));

    Ok(())
}

fn verificar_comando_disponivel(comando: &str) -> bool {
    Command::new("which")
        .arg(comando)
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .status()
        .map(|s| s.success())
        .unwrap_or(false)
}

fn main() -> std::io::Result<()> {
    executar_comando_simples();
    println!();

    if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
        pipeline_de_comandos()?;
        enviar_para_stdin()?;
    }

    for cmd in &["git", "cargo", "rustc", "python3"] {
        println!("{cmd}: {}", if verificar_comando_disponivel(cmd) {
            "disponível"
        } else {
            "não encontrado"
        });
    }

    Ok(())
}

Processo em background com timeout

use std::process::{Command, Stdio};
use std::time::Duration;
use std::thread;

fn executar_com_timeout(
    programa: &str,
    args: &[&str],
    timeout: Duration,
) -> Result<String, String> {
    let mut filho = Command::new(programa)
        .args(args)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .map_err(|e| format!("Falha ao iniciar processo: {e}"))?;

    // Aguarda com timeout em thread separada
    let handle = thread::spawn(move || {
        filho.wait_with_output()
    });

    match handle.join().ok()
        .and_then(|r| r.ok())
    {
        Some(saida) if saida.status.success() => {
            Ok(String::from_utf8_lossy(&saida.stdout).to_string())
        }
        Some(saida) => {
            Err(format!(
                "Processo falhou ({}): {}",
                saida.status,
                String::from_utf8_lossy(&saida.stderr)
            ))
        }
        None => Err("Timeout ou erro ao aguardar processo".to_string()),
    }
}

fn main() {
    match executar_com_timeout("ls", &["-la", "/tmp"], Duration::from_secs(5)) {
        Ok(saida) => println!("Saída:
{saida}"),
        Err(e)    => eprintln!("Erro: {e}"),
    }
}

Variáveis de ambiente e argumentos

use std::env;

fn main() {
    // Argumentos da linha de comando
    let args: Vec<String> = env::args().collect();
    println!("Programa: {}", args[0]);
    println!("Argumentos: {:?}", &args[1..]);

    // Variáveis de ambiente
    match env::var("HOME") {
        Ok(home) => println!("HOME: {home}"),
        Err(_)   => println!("HOME não definida"),
    }

    // Com valor padrão
    let porta = env::var("PORT")
        .unwrap_or_else(|_| "8080".to_string());
    println!("Porta: {porta}");

    // Listar todas as variáveis de ambiente
    println!("
Variáveis de ambiente (primeiras 5):");
    for (chave, valor) in env::vars().take(5) {
        println!("  {chave}={valor}");
    }

    // Diretório atual
    match env::current_dir() {
        Ok(dir)  => println!("
Diretório atual: {}", dir.display()),
        Err(e)   => eprintln!("Erro ao obter diretório: {e}"),
    }

    // Diretório do executável
    match env::current_exe() {
        Ok(exe)  => println!("Executável: {}", exe.display()),
        Err(e)   => eprintln!("Erro: {e}"),
    }
}

Sinais Unix com signal-hook

Sinais Unix são notificações assíncronas enviadas a processos — SIGTERM para término gracioso, SIGINT para Ctrl+C, SIGHUP para recarregar configuração. A biblioteca signal-hook oferece tratamento seguro de sinais em Rust:

[dependencies]
signal-hook = "0.3"
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
tokio = { version = "1", features = ["full"] }
use signal_hook::consts::{SIGINT, SIGTERM, SIGHUP};
use signal_hook::iterator::Signals;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

fn servidor_com_sinais() {
    // Flag atômica para shutdown gracioso
    let rodando = Arc::new(AtomicBool::new(true));
    let rodando_clone = Arc::clone(&rodando);

    // Thread dedicada ao tratamento de sinais
    let mut sinais = Signals::new([SIGINT, SIGTERM, SIGHUP])
        .expect("Falha ao registrar sinais");

    thread::spawn(move || {
        for sinal in &mut sinais {
            match sinal {
                SIGINT | SIGTERM => {
                    println!("
[Sinal {}] Encerrando graciosamente...", sinal);
                    rodando_clone.store(false, Ordering::Relaxed);
                    break;
                }
                SIGHUP => {
                    println!("[SIGHUP] Recarregando configuração...");
                    // Em produção: recarrega config aqui
                }
                _ => unreachable!(),
            }
        }
    });

    // Loop principal do servidor
    println!("Servidor iniciado. Pressione Ctrl+C para encerrar.");
    let mut iteracao = 0u32;

    while rodando.load(Ordering::Relaxed) {
        iteracao += 1;
        println!("  Processando iteração {iteracao}...");
        thread::sleep(Duration::from_millis(500));
    }

    // Shutdown gracioso
    println!("Realizando limpeza...");
    thread::sleep(Duration::from_millis(100));
    println!("Servidor encerrado com sucesso.");
}

fn main() {
    servidor_com_sinais();
}

Shutdown gracioso com Tokio

Em aplicações assíncronas com Tokio, o padrão de shutdown gracioso é diferente:

use tokio::signal;
use tokio::sync::broadcast;
use tokio::time::{sleep, Duration};

async fn worker(
    id: u32,
    mut shutdown_rx: broadcast::Receiver<()>,
) {
    println!("Worker {id} iniciado");

    loop {
        tokio::select! {
            // Trabalho normal
            _ = sleep(Duration::from_millis(500)) => {
                println!("  Worker {id}: processando...");
            }

            // Recebeu sinal de shutdown
            _ = shutdown_rx.recv() => {
                println!("  Worker {id}: encerrando graciosamente");
                // Cleanup aqui: fechar conexões, flush buffers, etc.
                sleep(Duration::from_millis(100)).await;
                println!("  Worker {id}: encerrado");
                break;
            }
        }
    }
}

#[tokio::main]
async fn main() {
    let (shutdown_tx, _) = broadcast::channel::<()>(1);

    // Lança workers
    let mut handles = Vec::new();
    for id in 1..=3 {
        let rx = shutdown_tx.subscribe();
        handles.push(tokio::spawn(worker(id, rx)));
    }

    println!("Servidor com {} workers. Ctrl+C para encerrar.", handles.len());

    // Aguarda sinal de interrupção
    signal::ctrl_c()
        .await
        .expect("Falha ao instalar handler de Ctrl+C");

    println!("
Ctrl+C recebido — enviando shutdown para workers...");

    // Notifica todos os workers
    let _ = shutdown_tx.send(());

    // Aguarda todos os workers terminarem
    for handle in handles {
        let _ = handle.await;
    }

    println!("Todos os workers encerrados. Servidor finalizado.");
}

Um programa completo: monitor de sistema

Vamos combinar arquivos, processos e sinais em um monitor de sistema que coleta métricas e as registra em arquivo:

use std::fs::{File, OpenOptions};
use std::io::{BufWriter, Write};
use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};

#[derive(Debug)]
struct MetricasSistema {
    timestamp: u64,
    processos: usize,
    carga: f64,
    uso_disco_tmp: u64,
}

fn coletar_metricas() -> MetricasSistema {
    use std::time::{SystemTime, UNIX_EPOCH};

    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs();

    // Conta processos via ps
    let processos = Command::new("sh")
        .arg("-c")
        .arg("ps aux | wc -l")
        .output()
        .map(|o| {
            String::from_utf8_lossy(&o.stdout)
                .trim()
                .parse::<usize>()
                .unwrap_or(0)
                .saturating_sub(1) // remove linha de cabeçalho
        })
        .unwrap_or(0);

    // Lê carga do sistema no Linux
    let carga = if cfg!(target_os = "linux") {
        std::fs::read_to_string("/proc/loadavg")
            .ok()
            .and_then(|s| s.split_whitespace().next().map(str::to_string))
            .and_then(|s| s.parse::<f64>().ok())
            .unwrap_or(0.0)
    } else {
        0.0
    };

    // Uso do diretório /tmp
    let uso_disco_tmp = Command::new("du")
        .args(["-sb", "/tmp"])
        .output()
        .map(|o| {
            String::from_utf8_lossy(&o.stdout)
                .split_whitespace()
                .next()
                .and_then(|s| s.parse::<u64>().ok())
                .unwrap_or(0)
        })
        .unwrap_or(0);

    MetricasSistema {
        timestamp,
        processos,
        carga,
        uso_disco_tmp,
    }
}

fn formatar_bytes(bytes: u64) -> String {
    const KB: u64 = 1024;
    const MB: u64 = KB * 1024;
    const GB: u64 = MB * 1024;

    if bytes >= GB {
        format!("{:.1}GB", bytes as f64 / GB as f64)
    } else if bytes >= MB {
        format!("{:.1}MB", bytes as f64 / MB as f64)
    } else if bytes >= KB {
        format!("{:.1}KB", bytes as f64 / KB as f64)
    } else {
        format!("{bytes}B")
    }
}

fn iniciar_monitor(
    intervalo: Duration,
    arquivo_log: &str,
    rodando: Arc<AtomicBool>,
) -> std::io::Result<()> {
    let arquivo = OpenOptions::new()
        .create(true)
        .append(true)
        .open(arquivo_log)?;

    let mut escritor = BufWriter::new(arquivo);

    // Cabeçalho CSV
    writeln!(escritor, "timestamp,processos,carga,disco_tmp")?;
    escritor.flush()?;

    let inicio = Instant::now();
    let mut amostras = 0u32;

    println!("Monitor iniciado. Log: {arquivo_log}");
    println!("Intervalo: {}ms", intervalo.as_millis());
    println!("{:>12} {:>10} {:>8} {:>10}",
        "Timestamp", "Processos", "Carga", "Disco/tmp");
    println!("{}", "─".repeat(44));

    while rodando.load(Ordering::Relaxed) {
        let metricas = coletar_metricas();
        amostras += 1;

        // Log em arquivo
        writeln!(
            escritor,
            "{},{},{:.2},{}",
            metricas.timestamp,
            metricas.processos,
            metricas.carga,
            metricas.uso_disco_tmp,
        )?;

        // Flush periódico — a cada 10 amostras
        if amostras % 10 == 0 {
            escritor.flush()?;
        }

        // Exibição no terminal
        println!(
            "{:>12} {:>10} {:>8.2} {:>10}",
            metricas.timestamp,
            metricas.processos,
            metricas.carga,
            formatar_bytes(metricas.uso_disco_tmp),
        );

        thread::sleep(intervalo);
    }

    // Flush final garantido
    escritor.flush()?;

    println!("
── Relatório Final ──");
    println!("Amostras coletadas : {amostras}");
    println!("Tempo total        : {:.1}s", inicio.elapsed().as_secs_f64());
    println!("Log salvo em       : {arquivo_log}");

    Ok(())
}

fn main() -> std::io::Result<()> {
    let rodando = Arc::new(AtomicBool::new(true));
    let rodando_ctrlc = Arc::clone(&rodando);

    // Handler de Ctrl+C
    ctrlc::set_handler(move || {
        println!("
[Interrompido] Encerrando...");
        rodando_ctrlc.store(false, Ordering::Relaxed);
    })
    .expect("Erro ao configurar handler Ctrl+C");

    iniciar_monitor(
        Duration::from_secs(2),
        "metricas.csv",
        rodando,
    )
}

Adicione ao Cargo.toml:

ctrlc = "3"

Permissões e atributos de arquivo

use std::fs;
use std::os::unix::fs::PermissionsExt; // apenas Unix

fn gerenciar_permissoes(caminho: &str) -> std::io::Result<()> {
    // Criar arquivo de teste
    fs::write(caminho, "conteúdo secreto")?;

    // Ler permissões atuais
    let meta = fs::metadata(caminho)?;
    let perms = meta.permissions();
    println!("Permissões atuais: {:o}", perms.mode() & 0o777);

    // Tornar somente leitura
    let mut novas_perms = perms.clone();
    novas_perms.set_readonly(true);
    fs::set_permissions(caminho, novas_perms)?;

    println!("Após set_readonly:");
    let meta = fs::metadata(caminho)?;
    println!("  Somente leitura: {}", meta.permissions().readonly());

    // Restaurar permissões normais (Unix)
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        let perms = fs::Permissions::from_mode(0o644);
        fs::set_permissions(caminho, perms)?;
        println!("Permissões restauradas para 644");
    }

    fs::remove_file(caminho)?;
    Ok(())
}

fn main() -> std::io::Result<()> {
    gerenciar_permissoes("arquivo_teste.txt")
}

Arquivos temporários com tempfile

Para arquivos temporários seguros:

tempfile = "3"
use tempfile::{tempdir, NamedTempFile};
use std::io::{Write, Read};

fn usar_arquivos_temporarios() -> std::io::Result<()> {
    // Arquivo temporário — deletado ao sair de escopo
    let mut arq_temp = NamedTempFile::new()?;
    writeln!(arq_temp, "Dados temporários")?;

    println!("Arquivo temp: {}", arq_temp.path().display());

    let mut conteudo = String::new();
    arq_temp.reopen()?.read_to_string(&mut conteudo)?;
    println!("Conteúdo: {conteudo}");
    // arq_temp deletado automaticamente aqui

    // Diretório temporário
    let dir_temp = tempdir()?;
    let caminho_arquivo = dir_temp.path().join("dados.txt");

    std::fs::write(&caminho_arquivo, "dados no dir temp")?;
    println!("Dir temp: {}", dir_temp.path().display());
    println!("Arquivo: {}", caminho_arquivo.display());

    // dir_temp e tudo dentro é deletado ao sair de escopo
    Ok(())
}

fn main() -> std::io::Result<()> {
    usar_arquivos_temporarios()
}

Fontes e leituras recomendadas

  • Rust Standard Library — std::fs — https://doc.rust-lang.org/std/fs/
  • Rust Standard Library — std::process — https://doc.rust-lang.org/std/process/
  • Rust Standard Library — std::io — https://doc.rust-lang.org/std/io/
  • signal-hook crate — tratamento seguro de sinais Unix — https://docs.rs/signal-hook
  • tempfile crate — arquivos e diretórios temporários — https://docs.rs/tempfile
  • ctrlc crate — handler portável para Ctrl+C — https://docs.rs/ctrlc
  • "The Linux Programming Interface" — Michael Kerrisk — referência definitiva para programação de sistemas em Unix — https://man7.org/tlpi/
  • "Rust for Systems Programming" — série de artigos — https://without.boats/blog/
Comentários

Mais em Rust

Macros — Escrevendo Código que Escreve Código
Macros — Escrevendo Código que Escreve Código

Desde o primeiro artigo usamos macros sem entender completamente o que são: p...

Traits — Definindo Comportamento Compartilhado
Traits — Definindo Comportamento Compartilhado

&nbsp; Nos artigos anteriores criamos structs e enums para modelar dados. Ma...

Testes Avançados — Propriedades, Mocks e Benchmarks
Testes Avançados — Propriedades, Mocks e Benchmarks

No Artigo #16 cobrimos os fundamentos de testes em Rust — testes unitários, d...