Do Python ao Rust: a evolução da execução de comandos
\n\nSe você já se perguntou "How do I invoke a system command and capture its output?" no Stack Overflow, sabe que esta é uma das perguntas mais populares e vitais para qualquer desenvolvedor. No mundo Python, a resposta é simples — subprocess.check_output(). Mas o que fazer se você migrou para o Rust e quer não apenas executar um comando, mas fazê-lo de forma elegante, segura e com máxima performance?
Rust não é apenas "mais uma linguagem". É um sistema onde cada byte está sob controle, e erros na execução de comandos não levam à falha de toda a aplicação. Em 2025, quando segurança e velocidade se tornaram o padrão de facto, saber trabalhar corretamente com subprocessos em Rust é uma habilidade indispensável. Vamos analisar as melhores práticas que transformarão a execução de comandos do sistema de uma rotina em uma arte.
\n\nstd::process::Command: seu canivete suíço
\n\nO coração da execução de comandos em Rust é o módulo std::process::Command. Ele fornece tudo o que é necessário para executar programas externos, capturar sua saída e tratar erros. Ao contrário do Python, onde subprocess esconde muitos detalhes, o Rust lhe dá controle total.
Exemplo básico: captura de stdout
\n\nVamos começar com o clássico — executar o comando echo "Hello, Rust!" e capturar sua saída:
use std::process::Command;\n\nfn main() -> Result<(), Box<dyn std::error::Error>> {\n let output = Command::new("echo")\n .arg("Hello, Rust!")\n .output()?;\n\n let stdout = String::from_utf8(output.stdout)?;\n println!("Saída do comando: {}", stdout);\n\n Ok(())\n}\n\n\nO que é importante aqui? Usamos .output(), que retorna uma estrutura Output. Ela contém três campos: stdout, stderr e status. Observe o ? — este é o operador de propagação de erros, que nos livra de blocos match volumosos. O Rust nos obriga a tratar explicitamente possíveis falhas: o comando pode não existir, não ter permissões de acesso ou simplesmente terminar com erro.
Tratamento de stderr e código de retorno
\n\nEm cenários reais, você precisa não apenas do stdout, mas também do stderr. Imagine que você executa ls /nonexistent — um comando que certamente gerará um erro:
use std::process::Command;\n\nfn main() {\n let output = Command::new("ls")\n .arg("/nonexistent")\n .output()\n .expect("Falha ao executar o comando");\n\n if output.status.success() {\n let stdout = String::from_utf8_lossy(&output.stdout);\n println!("Sucesso: {}", stdout);\n } else {\n let stderr = String::from_utf8_lossy(&output.stderr);\n eprintln!("Erro (código {}): {}", output.status.code().unwrap_or(-1), stderr);\n }\n}\n\n\nAqui usamos String::from_utf8_lossy() — uma forma segura de converter bytes em string, que substitui caracteres UTF-8 inválidos por �. Isso é crítico se a saída do comando contiver dados binários ou usar uma codificação imprevisível.
Técnicas avançadas: assincronia e pipes
\n\nEm 2025, a execução síncrona de comandos é coisa do passado. Aplicações modernas exigem assincronia, especialmente se você trabalha com servidores web ou processa vários comandos simultaneamente. O Rust, com seu ecossistema Tokio, oferece uma solução elegante.
\n\nCaptura assíncrona de saída com Tokio
\n\nAdicione a dependência no Cargo.toml:
[dependencies]\ntokio = { version = "1", features = ["full"] }\n\n\nAgora execute o comando de forma assíncrona:
\n\nuse tokio::process::Command;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let output = Command::new("sleep")\n .arg("2")\n .output()\n