<h2>Introdução aos Processos em Rust</h2>
<p>A execução de subprocessos é uma necessidade comum em aplicações modernas. Seja para chamar utilitários do sistema, executar scripts ou integrar ferramentas externas, Rust oferece através do módulo <code>std::process</code> uma API poderosa e segura para esse propósito. Diferentemente de linguagens como C, onde gerenciar processos pode ser perigoso e propenso a erros, Rust fornece abstrações que garantem segurança de memória mesmo ao trabalhar com subprocessos. Nesta aula, vamos explorar como criar, configurar e controlar subprocessos de forma eficiente.</p>
<h2>Criando e Executando Subprocessos</h2>
<h3>Comando Simples com <code>Command</code></h3>
<p>A estrutura fundamental é <code>Command</code>, que representa um processo a ser executado. Para criar um comando básico, usamos o construtor <code>new()</code> passando o executável desejado:</p>
<pre><code class="language-rust">use std::process::Command;
fn main() {
let output = Command::new("echo")
.arg("Olá, Rust!")
.output()
.expect("Falha ao executar comando");
println!("Status: {}", output.status);
println!("Stdout: {}", String::from_utf8_lossy(&output.stdout));
}</code></pre>
<p>O método <code>output()</code> aguarda a conclusão do processo e retorna um <code>Result<Output></code> contendo o status, stdout e stderr. Essa é a forma mais simples quando você precisa do resultado completo. Para processos que podem falhar, sempre trate o <code>Result</code> apropriadamente com <code>expect()</code>, <code>unwrap()</code> ou tratamento de erro customizado.</p>
<h3>Streaming com <code>spawn()</code></h3>
<p>Quando o processo é longo ou produz muitos dados, usar <code>output()</code> mantém tudo na memória. O método <code>spawn()</code> retorna um <code>Child</code> — uma representação do processo em execução — permitindo trabalhar com streams:</p>
<pre><code class="language-rust">use std::process::{Command, Stdio};
use std::io::{BufRead, BufReader};
fn main() {
let mut child = Command::new("ls")
.arg("-la")
.stdout(Stdio::piped())
.spawn()
.expect("Falha ao iniciar processo");
let stdout = child.stdout.take().expect("Falha ao capturar stdout");
let reader = BufReader::new(stdout);
for line in reader.lines() {
if let Ok(line) = line {
println!("{}", line);
}
}
let status = child.wait().expect("Falha ao aguardar processo");
println!("Processo terminou com: {}", status.code().unwrap_or(-1));
}</code></pre>
<p>Aqui usamos <code>Stdio::piped()</code> para capturar a saída padrão. Note que <code>stdout</code> é uma <code>Option</code> que precisa ser extraída com <code>take()</code>. Esse padrão evita que múltiplas partes do código tentem acessar o mesmo recurso simultaneamente, mantendo a segurança.</p>
<h2>Configuração Avançada de Subprocessos</h2>
<h3>Variáveis de Ambiente e Diretório de Trabalho</h3>
<p>Frequentemente é necessário passar variáveis de ambiente ou executar em um diretório específico. <code>Command</code> fornece métodos para ambos:</p>
<pre><code class="language-rust">use std::process::Command;
fn main() {
let output = Command::new("bash")
.arg("-c")
.arg("echo $MY_VAR")
.env("MY_VAR", "Valor_Customizado")
.current_dir("/tmp")
.output()
.expect("Falha ao executar");
println!("{}", String::from_utf8_lossy(&output.stdout));
}</code></pre>
<p>O método <code>env()</code> define variáveis individuais, enquanto <code>envs()</code> aceita um iterador. Para limpar todas as variáveis existentes e usar apenas as configuradas, use <code>env_clear()</code> antes de adicionar as suas.</p>
<h3>Tratamento de Stdin e Redirecionamento</h3>
<p>Alguns processos requerem entrada. Use <code>Stdio::piped()</code> para stdin também:</p>
<pre><code class="language-rust">use std::process::{Command, Stdio};
use std::io::Write;
fn main() {
let mut child = Command::new("cat")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Falha ao iniciar cat");
{
let stdin = child.stdin.as_mut().expect("Falha ao abrir stdin");
stdin.write_all(b"Dados para o cat\n")
.expect("Falha ao escrever");
} // stdin é dropado aqui, sinalizando EOF
let output = child.wait_with_output()
.expect("Falha ao aguardar");
println!("{}", String::from_utf8_lossy(&output.stdout));
}</code></pre>
<p>O bloco <code>{}</code> explícito garante que <code>stdin</code> é dropeado antes de chamar <code>wait_with_output()</code>, sinalizando fim de entrada ao processo filho.</p>
<h2>Padrões Importantes e Boas Práticas</h2>
<h3>Tratamento de Erros e Códigos de Saída</h3>
<p>Nem todo processo bem-sucedido retorna código zero. Verifique explicitamente o status:</p>
<pre><code class="language-rust">use std::process::Command;
fn main() {
let status = Command::new("grep")
.arg("inexistente")
.arg("arquivo.txt")
.status()
.expect("Falha ao executar grep");
match status.code() {
Some(0) => println!("Encontrado"),
Some(1) => println!("Não encontrado"),
Some(code) => println!("Erro: código {}", code),
None => println!("Processo terminado por sinal"),
}
}</code></pre>
<p>O método <code>status()</code> é mais leve que <code>output()</code> quando você só precisa do código de saída. <code>code()</code> retorna <code>Option<i32></code> — <code>None</code> indica morte por sinal em sistemas Unix.</p>
<h3>Processos em Paralelo</h3>
<p>Para executar múltiplos processos concorrentemente, mantenha referências a seus <code>Child</code>:</p>
<pre><code class="language-rust">use std::process::Command;
fn main() {
let mut children = vec![];
for i in 0..3 {
let child = Command::new("sleep")
.arg("1")
.spawn()
.expect("Falha ao iniciar");
children.push(child);
}
for mut child in children {
child.wait().expect("Falha ao aguardar");
}
println!("Todos os processos terminaram");
}</code></pre>
<p>Esse padrão permite que múltiplos processos executem simultaneamente. Para controle mais sofisticado, considere usar crates como <code>tokio</code> para processamento assíncrono.</p>
<h2>Conclusão</h2>
<p>Dominando <code>std::process</code>, você consegue integrar qualquer ferramenta externa de forma segura e eficiente. Os três pontos-chave aprendidos foram: <strong>(1) <code>Command</code> e <code>spawn()</code> são suas ferramentas principais</strong> — escolha <code>output()</code> para resultados pequenos e <code>spawn()</code> com streams para processos longos; <strong>(2) sempre trate variáveis de ambiente, diretórios e redirecionamentos explicitamente</strong> — Rust exige clareza, evitando bugs sutis; <strong>(3) verificar códigos de saída e gerenciar ciclos de vida é responsabilidade sua</strong> — o compilador não pode adivinhar a semântica do seu domínio.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://doc.rust-lang.org/std/process/" target="_blank" rel="noopener noreferrer">std::process - Rust Official Documentation</a></li>
<li><a href="https://doc.rust-lang.org/book/" target="_blank" rel="noopener noreferrer">The Rust Book - Running External Programs</a></li>
<li><a href="https://doc.rust-lang.org/rust-by-example/std_misc/process.html" target="_blank" rel="noopener noreferrer">Rust by Example - process</a></li>
<li><a href="https://tokio.rs/" target="_blank" rel="noopener noreferrer">Tokio Runtime - async subprocess spawning</a></li>
<li><a href="https://medium.com/swlh/rust-shell-commands-2bf8c6b3e6ce" target="_blank" rel="noopener noreferrer">Command Patterns in Rust - Medium Article</a></li>
</ul>