Introdução ao Clap: Por Que Usar?
O clap é a crate mais popular do Rust para parsing de argumentos de linha de comando. Diferentemente de fazer parsing manual com std::env::args(), o clap oferece uma API elegante, validação automática, geração de help text e tratamento de erros robusto. Se você está desenvolvendo ferramentas CLI em Rust, o clap é praticamente obrigatório — economiza horas de desenvolvimento e reduz drasticamente bugs relacionados a entrada do usuário.
O clap oferece duas abordagens principais: a API de builder e a API de atributos derive. Vou focar na abordagem derive (mais moderna e recomendada), que usa macros para gerar o parsing automaticamente a partir de structs Rust.
Instalação e Configuração Básica
Setup Inicial
Comece adicionando o clap ao seu Cargo.toml:
[dependencies]
clap = { version = "4.4", features = ["derive"] }
A feature derive ativa o macro #[derive(Parser)], essencial para a abordagem que usaremos. Agora, um exemplo funcional bem simples:
use clap::Parser;
#[derive(Parser, Debug)]
#[command(name = "MeuApp")]
#[command(about = "Uma ferramenta CLI demonstrativa", long_about = None)]
struct Args {
/// Nome do arquivo a processar
#[arg(short, long)]
arquivo: String,
/// Modo verboso
#[arg(short, long)]
verbose: bool,
}
fn main() {
let args = Args::parse();
println!("Arquivo: {}", args.arquivo);
println!("Verbose: {}", args.verbose);
}
Execute com cargo run -- --arquivo dados.txt --verbose e veja a mágica acontecer. O clap automaticamente valida se o arquivo foi fornecido, gera mensagens de erro amigáveis e oferece --help sem você escrever uma linha de código para isso.
Argumentos, Opções e Subcomandos
Argumentos vs Opções
Argumentos posicionais (sem -- ou -) e opções (com -- ou -) funcionam diferentemente no clap:
use clap::Parser;
#[derive(Parser, Debug)]
struct Args {
/// Argumento posicional obrigatório
#[arg(value_name = "ORIGEM")]
origem: String,
/// Argumento posicional opcional
#[arg(value_name = "DESTINO")]
destino: Option<String>,
/// Opção com valor
#[arg(short = 'o', long = "output")]
output: Option<String>,
/// Flag booleana (presente ou ausente)
#[arg(short, long)]
force: bool,
/// Múltiplos valores
#[arg(short = 'f', long = "filter")]
filtros: Vec<String>,
}
fn main() {
let args = Args::parse();
println!("Origem: {}", args.origem);
if let Some(dest) = args.destino {
println!("Destino: {}", dest);
}
println!("Filtros: {:?}", args.filtros);
}
Execute assim: cargo run -- arquivo.txt dest.txt -f json -f csv --force. O clap coleta múltiplos valores em Vec automaticamente.
Subcomandos
Para aplicações complexas, subcomandos são essenciais:
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "admin")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// Criar novo usuário
Create {
#[arg(short, long)]
name: String,
#[arg(short, long)]
email: String,
},
/// Deletar usuário
Delete {
#[arg(short, long)]
id: u32,
},
/// Listar todos os usuários
List {
#[arg(short, long)]
verbose: bool,
},
}
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::Create { name, email } => {
println!("Criando usuário: {} ({})", name, email);
}
Commands::Delete { id } => {
println!("Deletando usuário com ID: {}", id);
}
Commands::List { verbose } => {
println!("Listando usuários (verbose={})", verbose);
}
}
}
Use assim: cargo run -- create --name "João" --email "joao@email.com". Cada subcomando tem seus próprios argumentos e help text.
Validação, Valores Padrão e Comportamentos Avançados
Validação e Restrições
O clap oferece várias formas de validar entrada:
use clap::Parser;
#[derive(Parser, Debug)]
struct Args {
/// Número entre 1 e 100
#[arg(short, long, value_parser = clap::value_parser!(u32).range(1..=100))]
numero: u32,
/// Arquivo que deve existir
#[arg(short, long, value_parser = clap::builder::PathBufValueParser::new())]
arquivo: std::path::PathBuf,
/// Valor de um conjunto específico
#[arg(short, long, value_parser = ["json", "yaml", "toml"])]
formato: String,
/// Valor padrão
#[arg(short, long, default_value = "info")]
nivel: String,
}
fn main() {
let args = Args::parse();
println!("Número: {}, Arquivo: {:?}", args.numero, args.arquivo);
println!("Formato: {}, Nível: {}", args.formato, args.nivel);
}
Se executar cargo run -- --numero 150, o clap rejeita e exibe um erro claro. O value_parser faz validação automática.
Comportamentos Avançados
Para casos mais complexos, use atributos customizados:
use clap::Parser;
#[derive(Parser, Debug)]
struct Args {
/// Requer que outra flag esteja presente
#[arg(short, long, requires = "token")]
autenticado: bool,
/// Conflita com outra flag
#[arg(long, conflicts_with = "output")]
stdout: bool,
#[arg(long)]
output: Option<String>,
/// Token (obrigatório se 'autenticado' estiver presente)
#[arg(short, long)]
token: Option<String>,
/// Variável de ambiente como fallback
#[arg(short, long, env = "APP_DEBUG")]
debug: bool,
}
fn main() {
let args = Args::parse();
println!("{:?}", args);
}
Experimente: cargo run -- --autenticado (vai falhar porque precisa de --token). Ou cargo run -- --autenticado --token abc123 (funciona).
Conclusão
Aprendemos que o clap simplifica drasticamente o desenvolvimento de CLIs em Rust, eliminando boilerplate e oferecendo validação robusta out-of-the-box. A abordagem derive é mais legível e manutenível do que construção manual de parsers. E, finalmente, o clap escala bem: de aplicações simples com um único comando até sistemas complexos com múltiplos subcomandos, validações customizadas e comportamentos avançados.
Na prática, 95% dos seus problemas de parsing será resolvido com os conceitos aqui apresentados. O restante está bem documentado na documentação oficial.