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-hookcrate — tratamento seguro de sinais Unix — https://docs.rs/signal-hooktempfilecrate — arquivos e diretórios temporários — https://docs.rs/tempfilectrlccrate — 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/