Ler Argumentos da Linha de Comando em Rust

Aprenda como ler argumentos da linha de comando em Rust com std::env::args e clap. Derive macros, validação, subcomandos e exemplos completos.

Ler Argumentos da Linha de Comando em Rust

Processar argumentos de linha de comando é fundamental para criar ferramentas CLI. Rust oferece std::env::args na biblioteca padrão e a crate clap para parsing avançado com validação automática.

Método básico: std::env::args

A forma mais simples, sem dependências externas:

use std::env;

fn main() {
    // args() retorna um iterador sobre os argumentos
    let args: Vec<String> = env::args().collect();

    println!("Total de argumentos: {}", args.len());
    println!("Programa: {}", args[0]);

    // Acessar argumentos por posição
    if args.len() > 1 {
        println!("Primeiro argumento: {}", args[1]);
    }

    // Iterar sobre todos os argumentos (pulando o nome do programa)
    println!("\nArgumentos:");
    for (i, arg) in args.iter().skip(1).enumerate() {
        println!("  [{}] = {}", i, arg);
    }

    // Exemplo prático: calculadora simples
    if args.len() == 4 {
        let a: f64 = args[1].parse().unwrap_or(0.0);
        let op = &args[2];
        let b: f64 = args[3].parse().unwrap_or(0.0);

        let resultado = match op.as_str() {
            "+" => a + b,
            "-" => a - b,
            "*" | "x" => a * b,
            "/" => if b != 0.0 { a / b } else { f64::NAN },
            _ => {
                eprintln!("Operador desconhecido: {}", op);
                return;
            }
        };

        println!("{} {} {} = {}", a, op, b, resultado);
    } else if args.len() > 1 {
        eprintln!("Uso: {} <número> <operador> <número>", args[0]);
    }
}

Execução e saída:

$ cargo run -- 10 + 32
Total de argumentos: 4
Programa: target/debug/meu_app
Primeiro argumento: 10

Argumentos:
  [0] = 10
  [1] = +
  [2] = 32
10 + 32 = 42

Parsing manual de flags

Processe flags e opções manualmente:

use std::env;
use std::process;

struct Config {
    arquivo: String,
    linhas: usize,
    verbose: bool,
    formato: String,
}

fn parse_args() -> Config {
    let args: Vec<String> = env::args().collect();
    let mut config = Config {
        arquivo: String::new(),
        linhas: 10,
        verbose: false,
        formato: "texto".to_string(),
    };

    let mut i = 1;
    while i < args.len() {
        match args[i].as_str() {
            "-n" | "--linhas" => {
                i += 1;
                config.linhas = args.get(i)
                    .and_then(|v| v.parse().ok())
                    .unwrap_or_else(|| {
                        eprintln!("Erro: -n requer um número");
                        process::exit(1);
                    });
            }
            "-v" | "--verbose" => config.verbose = true,
            "-f" | "--formato" => {
                i += 1;
                config.formato = args.get(i).cloned().unwrap_or_else(|| {
                    eprintln!("Erro: -f requer um valor");
                    process::exit(1);
                });
            }
            "-h" | "--help" => {
                println!("Uso: programa [OPÇÕES] <arquivo>");
                println!("  -n, --linhas NUM    Número de linhas (padrão: 10)");
                println!("  -v, --verbose       Modo verboso");
                println!("  -f, --formato FMT   Formato de saída (texto/json)");
                println!("  -h, --help          Mostrar ajuda");
                process::exit(0);
            }
            arg if arg.starts_with('-') => {
                eprintln!("Opção desconhecida: {}", arg);
                process::exit(1);
            }
            _ => config.arquivo = args[i].clone(),
        }
        i += 1;
    }

    if config.arquivo.is_empty() {
        eprintln!("Erro: arquivo é obrigatório. Use --help para ajuda.");
        process::exit(1);
    }

    config
}

fn main() {
    let config = parse_args();

    println!("Arquivo: {}", config.arquivo);
    println!("Linhas: {}", config.linhas);
    println!("Verbose: {}", config.verbose);
    println!("Formato: {}", config.formato);
}

Execução:

$ cargo run -- -v -n 20 -f json dados.txt
Arquivo: dados.txt
Linhas: 20
Verbose: true
Formato: json

Clap com derive macros (recomendado)

A crate clap gera automaticamente parsing, validação e mensagens de ajuda:

Cargo.toml:

[dependencies]
clap = { version = "4", features = ["derive"] }
use clap::Parser;

/// Ferramenta de processamento de arquivos de texto
#[derive(Parser, Debug)]
#[command(name = "textproc", version, about)]
struct Args {
    /// Arquivo de entrada para processar
    arquivo: String,

    /// Número máximo de linhas para ler
    #[arg(short = 'n', long, default_value_t = 10)]
    linhas: usize,

    /// Padrão de busca (regex)
    #[arg(short, long)]
    busca: Option<String>,

    /// Formato de saída
    #[arg(short, long, default_value = "texto", value_parser = ["texto", "json", "csv"])]
    formato: String,

    /// Ativar modo verboso
    #[arg(short, long)]
    verbose: bool,

    /// Diretório de saída
    #[arg(short = 'o', long, default_value = ".")]
    output: String,
}

fn main() {
    let args = Args::parse();

    if args.verbose {
        println!("Modo verboso ativado");
        println!("Configuração: {:#?}", args);
    }

    println!("Processando: {}", args.arquivo);
    println!("Linhas: {}", args.linhas);
    println!("Formato: {}", args.formato);
    println!("Output: {}", args.output);

    if let Some(busca) = &args.busca {
        println!("Buscando: '{}'", busca);
    }
}

Execução e saída:

$ cargo run -- dados.txt -n 50 --formato json -v --busca "erro"
Modo verboso ativado
Configuração: Args { arquivo: "dados.txt", linhas: 50, busca: Some("erro"), formato: "json", verbose: true, output: "." }
Processando: dados.txt
Linhas: 50
Formato: json
Output: .
Buscando: 'erro'

$ cargo run -- --help
Ferramenta de processamento de arquivos de texto

Usage: textproc [OPTIONS] <ARQUIVO>

Arguments:
  <ARQUIVO>  Arquivo de entrada para processar

Options:
  -n, --linhas <LINHAS>    Número máximo de linhas [default: 10]
  -b, --busca <BUSCA>      Padrão de busca (regex)
  -f, --formato <FORMATO>  Formato de saída [default: texto] [possible values: texto, json, csv]
  -v, --verbose            Ativar modo verboso
  -o, --output <OUTPUT>    Diretório de saída [default: .]
  -h, --help               Print help
  -V, --version            Print version

Subcomandos com clap

Crie CLIs com subcomandos (como git commit, git push):

Cargo.toml:

[dependencies]
clap = { version = "4", features = ["derive"] }
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "tarefas", about = "Gerenciador de tarefas CLI")]
struct Cli {
    #[command(subcommand)]
    comando: Comandos,
}

#[derive(Subcommand)]
enum Comandos {
    /// Adicionar uma nova tarefa
    Add {
        /// Descrição da tarefa
        descricao: String,

        /// Prioridade (1-5)
        #[arg(short, long, default_value_t = 3)]
        prioridade: u8,
    },

    /// Listar todas as tarefas
    List {
        /// Mostrar apenas tarefas com esta prioridade
        #[arg(short, long)]
        prioridade: Option<u8>,

        /// Mostrar tarefas concluídas
        #[arg(long)]
        concluidas: bool,
    },

    /// Marcar tarefa como concluída
    Done {
        /// ID da tarefa
        id: u32,
    },

    /// Remover uma tarefa
    Remove {
        /// ID da tarefa
        id: u32,

        /// Não pedir confirmação
        #[arg(short, long)]
        force: bool,
    },
}

fn main() {
    let cli = Cli::parse();

    match cli.comando {
        Comandos::Add { descricao, prioridade } => {
            println!("Nova tarefa: '{}' (prioridade: {})", descricao, prioridade);
        }
        Comandos::List { prioridade, concluidas } => {
            println!("Listando tarefas:");
            if let Some(p) = prioridade {
                println!("  Filtro: prioridade = {}", p);
            }
            println!("  Mostrar concluídas: {}", concluidas);
        }
        Comandos::Done { id } => {
            println!("Tarefa #{} marcada como concluída!", id);
        }
        Comandos::Remove { id, force } => {
            if force {
                println!("Tarefa #{} removida (sem confirmação)", id);
            } else {
                println!("Tem certeza que deseja remover tarefa #{}? (use --force)", id);
            }
        }
    }
}

Execução:

$ cargo run -- add "Aprender Rust" --prioridade 5
Nova tarefa: 'Aprender Rust' (prioridade: 5)

$ cargo run -- list --prioridade 5
Listando tarefas:
  Filtro: prioridade = 5
  Mostrar concluídas: false

$ cargo run -- done 42
Tarefa #42 marcada como concluída!

Veja também