Rust Admin

Boas Práticas de Processos e Subprocessos em Rust com std::process para Times Ágeis Já leu

Introdução aos Processos em Rust 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 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. Criando e Executando Subprocessos Comando Simples com A estrutura fundamental é , que representa um processo a ser executado. Para criar um comando básico, usamos o construtor passando o executável desejado: O método aguarda a conclusão do processo e retorna um 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 apropriadamente com , ou tratamento de erro customizado. Streaming com Quando

Introdução aos Processos em Rust

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 std::process 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.

Criando e Executando Subprocessos

Comando Simples com Command

A estrutura fundamental é Command, que representa um processo a ser executado. Para criar um comando básico, usamos o construtor new() passando o executável desejado:

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));
}

O método output() aguarda a conclusão do processo e retorna um Result<Output> 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 Result apropriadamente com expect(), unwrap() ou tratamento de erro customizado.

Streaming com spawn()

Quando o processo é longo ou produz muitos dados, usar output() mantém tudo na memória. O método spawn() retorna um Child — uma representação do processo em execução — permitindo trabalhar com streams:

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));
}

Aqui usamos Stdio::piped() para capturar a saída padrão. Note que stdout é uma Option que precisa ser extraída com take(). Esse padrão evita que múltiplas partes do código tentem acessar o mesmo recurso simultaneamente, mantendo a segurança.

Configuração Avançada de Subprocessos

Variáveis de Ambiente e Diretório de Trabalho

Frequentemente é necessário passar variáveis de ambiente ou executar em um diretório específico. Command fornece métodos para ambos:

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));
}

O método env() define variáveis individuais, enquanto envs() aceita um iterador. Para limpar todas as variáveis existentes e usar apenas as configuradas, use env_clear() antes de adicionar as suas.

Tratamento de Stdin e Redirecionamento

Alguns processos requerem entrada. Use Stdio::piped() para stdin também:

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));
}

O bloco {} explícito garante que stdin é dropeado antes de chamar wait_with_output(), sinalizando fim de entrada ao processo filho.

Padrões Importantes e Boas Práticas

Tratamento de Erros e Códigos de Saída

Nem todo processo bem-sucedido retorna código zero. Verifique explicitamente o status:

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"),
    }
}

O método status() é mais leve que output() quando você só precisa do código de saída. code() retorna Option<i32>None indica morte por sinal em sistemas Unix.

Processos em Paralelo

Para executar múltiplos processos concorrentemente, mantenha referências a seus Child:

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");
}

Esse padrão permite que múltiplos processos executem simultaneamente. Para controle mais sofisticado, considere usar crates como tokio para processamento assíncrono.

Conclusão

Dominando std::process, você consegue integrar qualquer ferramenta externa de forma segura e eficiente. Os três pontos-chave aprendidos foram: (1) Command e spawn() são suas ferramentas principais — escolha output() para resultados pequenos e spawn() com streams para processos longos; (2) sempre trate variáveis de ambiente, diretórios e redirecionamentos explicitamente — Rust exige clareza, evitando bugs sutis; (3) verificar códigos de saída e gerenciar ciclos de vida é responsabilidade sua — o compilador não pode adivinhar a semântica do seu domínio.

Referências


Artigos relacionados