Clap Rust: Guia Completo para CLIs em 2026 | Rust Brasil

Guia completo do Clap 4 em Rust: derive API, subcomandos, validação e auto-completions. Crie CLIs profissionais em Rust.

Introdução

Criar aplicações de linha de comando (CLI) é um dos casos de uso mais populares do Rust, e o Clap é a biblioteca padrão para parsing de argumentos. Com a versão 4, o Clap consolidou a derive API que torna a criação de CLIs tão simples quanto definir uma struct.

Historicamente, existia o StructOpt como uma camada derive separada sobre o Clap. Desde o Clap 3, a derive API foi incorporada diretamente ao Clap, tornando o StructOpt obsoleto. Se você está usando StructOpt, este artigo também serve como guia de migração.

Neste guia, vamos cobrir desde o básico até funcionalidades avançadas como subcomandos, validação customizada, auto-completions e output colorido.

Dependências no Cargo.toml

[dependencies]
clap = { version = "4", features = ["derive"] }

Para funcionalidades extras:

[dependencies]
clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] }
clap_complete = "4"    # Auto-completions para shells
anstyle = "1"          # Cores no help

Tabela: Clap 4 vs StructOpt

CaracterísticaClap 4 (Derive)StructOpt
StatusAtivo, versão atualDeprecado
API#[derive(Parser)]#[derive(StructOpt)]
Atributo#[arg(...)] e #[command(...)]#[structopt(...)]
Subcomandos#[derive(Subcommand)]#[derive(StructOpt)] com enum
Validaçãovalue_parservalidator
Completionsclap_completestructopt::clap::Shell
PerformanceOtimizadaOverhead adicional

CLI Básica

use clap::Parser;

/// Ferramenta para gerenciar tarefas
#[derive(Parser, Debug)]
#[command(name = "tarefas")]
#[command(version, about, long_about = None)]
struct Cli {
    /// Nome da tarefa a ser criada
    nome: String,

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

    /// Marcar como concluída
    #[arg(short, long)]
    concluida: bool,

    /// Tags da tarefa
    #[arg(short, long)]
    tags: Vec<String>,

    /// Modo verbose
    #[arg(short, long, action = clap::ArgAction::Count)]
    verbose: u8,
}

fn main() {
    let cli = Cli::parse();
    println!("{cli:#?}");

    match cli.verbose {
        0 => println!("Tarefa: {}", cli.nome),
        1 => println!("Tarefa: {} (prioridade: {})", cli.nome, cli.prioridade),
        _ => println!("Tarefa: {:?}", cli),
    }
}

Uso:

$ tarefas "Estudar Rust" --prioridade 5 -t rust -t estudo -v
Tarefa: Estudar Rust (prioridade: 5)

Help gerado automaticamente:

Ferramenta para gerenciar tarefas

Usage: tarefas [OPTIONS] <NOME>

Arguments:
  <NOME>  Nome da tarefa a ser criada

Options:
  -p, --prioridade <PRIORIDADE>  Prioridade da tarefa (1-5) [default: 3]
  -c, --concluida                Marcar como concluída
  -t, --tags <TAGS>              Tags da tarefa
  -v, --verbose...               Modo verbose
  -h, --help                     Print help
  -V, --version                  Print version

Subcomandos

Subcomandos são definidos como enums com #[derive(Subcommand)]:

use clap::{Parser, Subcommand};
use std::path::PathBuf;

#[derive(Parser, Debug)]
#[command(name = "proj")]
#[command(about = "Gerenciador de projetos Rust")]
struct Cli {
    #[command(subcommand)]
    comando: Comandos,
}

#[derive(Subcommand, Debug)]
enum Comandos {
    /// Criar um novo projeto
    Novo {
        /// Nome do projeto
        nome: String,

        /// Template a usar
        #[arg(short, long, default_value = "basico")]
        template: String,
    },

    /// Compilar o projeto
    Build {
        /// Compilar em modo release
        #[arg(short, long)]
        release: bool,

        /// Diretório de saída
        #[arg(short, long)]
        output: Option<PathBuf>,
    },

    /// Executar testes
    Test {
        /// Filtro de testes
        filtro: Option<String>,

        /// Executar testes ignorados
        #[arg(long)]
        include_ignored: bool,
    },

    /// Publicar no crates.io
    Publish {
        /// Dry run (sem publicar de verdade)
        #[arg(long)]
        dry_run: bool,
    },
}

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

    match cli.comando {
        Comandos::Novo { nome, template } => {
            println!("Criando projeto '{nome}' com template '{template}'");
        }
        Comandos::Build { release, output } => {
            let modo = if release { "release" } else { "debug" };
            println!("Compilando em modo {modo}");
            if let Some(path) = output {
                println!("Saída: {}", path.display());
            }
        }
        Comandos::Test { filtro, include_ignored } => {
            println!("Executando testes");
            if let Some(f) = filtro {
                println!("Filtro: {f}");
            }
            if include_ignored {
                println!("Incluindo testes ignorados");
            }
        }
        Comandos::Publish { dry_run } => {
            if dry_run {
                println!("Dry run: verificando publicação...");
            } else {
                println!("Publicando...");
            }
        }
    }
}

Validação de Argumentos

Validação com value_parser

use clap::Parser;
use std::path::PathBuf;

#[derive(Parser, Debug)]
struct Cli {
    /// Porta do servidor (1024-65535)
    #[arg(short, long, value_parser = clap::value_parser!(u16).range(1024..=65535))]
    porta: u16,

    /// Arquivo de entrada (deve existir)
    #[arg(short, long, value_parser = validar_arquivo_existe)]
    entrada: PathBuf,

    /// Nível de log
    #[arg(short, long, value_parser = ["debug", "info", "warn", "error"])]
    log_level: String,

    /// Email do usuário
    #[arg(short, long, value_parser = validar_email)]
    email: String,
}

fn validar_arquivo_existe(s: &str) -> Result<PathBuf, String> {
    let path = PathBuf::from(s);
    if path.exists() {
        Ok(path)
    } else {
        Err(format!("Arquivo não encontrado: {s}"))
    }
}

fn validar_email(s: &str) -> Result<String, String> {
    if s.contains('@') && s.contains('.') {
        Ok(s.to_string())
    } else {
        Err(format!("Email inválido: {s}"))
    }
}

fn main() {
    let cli = Cli::parse();
    println!("{cli:#?}");
}

Valores de Enums

use clap::{Parser, ValueEnum};

#[derive(Debug, Clone, ValueEnum)]
enum Formato {
    Json,
    Yaml,
    Toml,
    Csv,
}

#[derive(Parser, Debug)]
struct Cli {
    /// Formato de saída
    #[arg(short, long, value_enum, default_value_t = Formato::Json)]
    formato: Formato,
}

fn main() {
    let cli = Cli::parse();
    match cli.formato {
        Formato::Json => println!("Saída em JSON"),
        Formato::Yaml => println!("Saída em YAML"),
        Formato::Toml => println!("Saída em TOML"),
        Formato::Csv => println!("Saída em CSV"),
    }
}

Variáveis de Ambiente

use clap::Parser;

#[derive(Parser, Debug)]
struct Cli {
    /// URL do banco de dados
    #[arg(long, env = "DATABASE_URL")]
    database_url: String,

    /// Token de API
    #[arg(long, env = "API_TOKEN", hide_env_values = true)]
    api_token: String,

    /// Porta do servidor
    #[arg(short, long, env = "PORT", default_value_t = 8080)]
    porta: u16,
}

fn main() {
    let cli = Cli::parse();
    println!("DB: {}", cli.database_url);
    println!("Porta: {}", cli.porta);
}

Auto-Completions para Shells

Gerar scripts de auto-complete para bash, zsh, fish e PowerShell:

use clap::{Parser, Subcommand};
use clap_complete::{generate, Shell};
use std::io;

#[derive(Parser)]
#[command(name = "minha-cli")]
struct Cli {
    #[command(subcommand)]
    comando: Comandos,
}

#[derive(Subcommand)]
enum Comandos {
    /// Executar a aplicação
    Run,
    /// Gerar completions para o shell
    Completions {
        /// Shell alvo
        #[arg(value_enum)]
        shell: Shell,
    },
}

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

    match cli.comando {
        Comandos::Run => {
            println!("Executando...");
        }
        Comandos::Completions { shell } => {
            let mut cmd = <Cli as clap::CommandFactory>::command();
            generate(shell, &mut cmd, "minha-cli", &mut io::stdout());
        }
    }
}

Uso:

# Gerar completions para bash
minha-cli completions bash > /etc/bash_completion.d/minha-cli

# Para zsh
minha-cli completions zsh > ~/.zfunc/_minha-cli

# Para fish
minha-cli completions fish > ~/.config/fish/completions/minha-cli.fish

Output Colorido

Para output colorido no terminal, combine com crates como colored ou owo-colors:

[dependencies]
clap = { version = "4", features = ["derive"] }
colored = "2"
indicatif = "0.17"  # Barras de progresso
use clap::Parser;
use colored::Colorize;

#[derive(Parser)]
struct Cli {
    #[command(subcommand)]
    comando: Comando,
}

#[derive(clap::Subcommand)]
enum Comando {
    /// Verificar status do projeto
    Status,
    /// Compilar o projeto
    Build,
}

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

    match cli.comando {
        Comando::Status => {
            println!("{} Verificando status...", "●".blue());
            println!("  {} 3 testes passando", "✓".green());
            println!("  {} 1 warning", "!".yellow());
            println!("  {} Nenhum erro", "✓".green());
        }
        Comando::Build => {
            println!("{}", "Compilando projeto...".bold());
            println!("  {} src/main.rs", "Compilando".green().bold());
            println!("  {} Build completo em 2.3s", "Sucesso:".green().bold());
        }
    }
}

Barras de Progresso com indicatif

use indicatif::{ProgressBar, ProgressStyle};
use std::thread;
use std::time::Duration;

fn main() {
    let pb = ProgressBar::new(100);
    pb.set_style(
        ProgressStyle::with_template(
            "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})"
        )
        .unwrap()
        .progress_chars("█▓░"),
    );

    for _ in 0..100 {
        pb.inc(1);
        thread::sleep(Duration::from_millis(30));
    }
    pb.finish_with_message("Concluído!");
}

Migração do StructOpt para Clap 4

Se você tem um projeto usando StructOpt, aqui está o guia de migração:

Dependências

# Antes (StructOpt)
[dependencies]
structopt = "0.3"

# Depois (Clap 4)
[dependencies]
clap = { version = "4", features = ["derive"] }

Derives e Atributos

// Antes (StructOpt)
use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(name = "minha-cli", about = "Descrição")]
struct Cli {
    #[structopt(short, long, default_value = "8080")]
    porta: u16,

    #[structopt(subcommand)]
    cmd: Comando,
}

#[derive(StructOpt)]
enum Comando {
    #[structopt(about = "Executar")]
    Run {
        #[structopt(short, long)]
        verbose: bool,
    },
}

fn main() {
    let cli = Cli::from_args();
}
// Depois (Clap 4)
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "minha-cli", about = "Descrição")]
struct Cli {
    #[arg(short, long, default_value_t = 8080)]
    porta: u16,

    #[command(subcommand)]
    cmd: Comando,
}

#[derive(Subcommand)]
enum Comando {
    /// Executar
    Run {
        #[arg(short, long)]
        verbose: bool,
    },
}

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

Principais mudanças na migração:

StructOptClap 4
#[derive(StructOpt)]#[derive(Parser)] ou #[derive(Subcommand)]
#[structopt(...)]#[arg(...)] para args, #[command(...)] para command
Cli::from_args()Cli::parse()
default_value = "8080"default_value_t = 8080 (tipado)
validator = "fn"value_parser = fn
possible_valuesvalue_enum ou value_parser

Padrões e Boas Práticas

  1. Use doc comments (///) como descrições — eles aparecem no --help
  2. Sempre defina #[command(version)] para que --version funcione
  3. Use value_enum para argumentos com valores finitos
  4. Prefira default_value_t sobre default_value para tipos não-String
  5. Use env para configurações que também podem vir de variáveis de ambiente
  6. Gere completions para melhorar a experiência do usuário
  7. Use hide = true para flags internas/de debug
use clap::Parser;

/// Minha ferramenta incrível para gerenciar deploys
#[derive(Parser)]
#[command(version, about, long_about = None)]
#[command(propagate_version = true)]
struct Cli {
    /// Ambiente alvo
    #[arg(short, long, value_enum)]
    ambiente: Ambiente,

    /// Flags de debug (interno)
    #[arg(long, hide = true)]
    debug_flags: Option<String>,
}

#[derive(Clone, clap::ValueEnum)]
enum Ambiente {
    Dev,
    Staging,
    Producao,
}

Conclusão

O Clap 4 é a ferramenta definitiva para criar CLIs em Rust. A derive API torna o processo declarativo e ergonômico, enquanto features como validação, subcomandos, completions e variáveis de ambiente cobrem praticamente qualquer necessidade. Se você ainda usa StructOpt, a migração para Clap 4 é simples e vale o esforço.

Veja Também