---
title: "Rust CLI: Crie Ferramentas de Linha de Comando | Rust Brasil"
url: "https://rustlang.com.br/artigos/rust-para-cli/"
markdown_url: "https://rustlang.com.br/artigos/rust-para-cli.MD"
description: "Crie CLIs profissionais em Rust com Clap e dialoguer. Saída colorida, distribuição multiplataforma e exemplos práticos."
date: "2026-02-23"
author: "Equipe Rust Brasil"
---

# Rust CLI: Crie Ferramentas de Linha de Comando | Rust Brasil

Crie CLIs profissionais em Rust com Clap e dialoguer. Saída colorida, distribuição multiplataforma e exemplos práticos.


## Introdução

Ferramentas de linha de comando são uma das áreas onde Rust mais brilha. As razões são claras: **binários estáticos** que não dependem de runtime ou interpretador, **startup instantâneo** (sem JIT ou carregamento de VM), **baixo consumo de memória** e a capacidade de distribuir um único executável para qualquer plataforma. Não é coincidência que algumas das ferramentas de terminal mais populares da última década foram escritas em Rust: **ripgrep**, **bat**, **eza**, **fd**, **dust**, **delta**, **zoxide** e **starship**.

Se você é um desenvolvedor que cria scripts em Python ou Bash e quer transformá-los em ferramentas robustas e rápidas, Rust é a escolha ideal. Neste artigo, vamos explorar o ecossistema de bibliotecas para CLIs, construir uma ferramenta completa e entender como distribuir seus binários para o mundo.

## Ecossistema de Bibliotecas para CLI

### Clap — Parser de Argumentos

Clap é a biblioteca padrão para parsing de argumentos de linha de comando. Suporta subcomandos, validação, autocompletion e geração automática de help:

```toml
[package]
name = "minha-cli"
version = "0.1.0"
edition = "2021"

[dependencies]
clap = { version = "4", features = ["derive"] }
indicatif = "0.17"
dialoguer = "0.11"
colored = "2"
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
```

### Indicatif — Barras de Progresso

Indicatif cria barras de progresso e spinners elegantes para operações demoradas.

### Dialoguer — Prompts Interativos

Dialoguer oferece prompts de confirmação, seleção, input de texto e senhas para CLIs interativas.

### Colored — Saída Colorida

Colored permite adicionar cores e estilos ao output do terminal de forma simples e expressiva.

## Exemplo Prático: Gerenciador de Projetos CLI

Vamos construir uma ferramenta completa de gerenciamento de projetos com subcomandos, barras de progresso e saída colorida.

```rust
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use colored::*;
use dialoguer::{Confirm, Input, Select};
use indicatif::{ProgressBar, ProgressStyle};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use std::time::Duration;

#[derive(Parser)]
#[command(name = "projetos")]
#[command(about = "Gerenciador de projetos no terminal", version)]
struct Cli {
    #[command(subcommand)]
    comando: Comandos,

    /// Caminho do arquivo de dados
    #[arg(short, long, default_value = "projetos.json")]
    arquivo: PathBuf,
}

#[derive(Subcommand)]
enum Comandos {
    /// Criar um novo projeto
    Novo {
        /// Nome do projeto
        nome: Option<String>,
    },
    /// Listar todos os projetos
    Listar {
        /// Filtrar por status
        #[arg(short, long)]
        status: Option<String>,
    },
    /// Marcar projeto como concluído
    Concluir {
        /// ID do projeto
        id: usize,
    },
    /// Remover um projeto
    Remover {
        /// ID do projeto
        id: usize,
    },
    /// Exportar relatório
    Relatorio,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
struct Projeto {
    id: usize,
    nome: String,
    descricao: String,
    status: String,
    prioridade: String,
}

#[derive(Debug, Serialize, Deserialize, Default)]
struct Dados {
    projetos: Vec<Projeto>,
    proximo_id: usize,
}

impl Dados {
    fn carregar(caminho: &PathBuf) -> Result<Self> {
        if caminho.exists() {
            let conteudo = fs::read_to_string(caminho)
                .context("Falha ao ler arquivo de dados")?;
            serde_json::from_str(&conteudo)
                .context("Falha ao parsear dados")
        } else {
            Ok(Dados {
                projetos: Vec::new(),
                proximo_id: 1,
            })
        }
    }

    fn salvar(&self, caminho: &PathBuf) -> Result<()> {
        let conteudo = serde_json::to_string_pretty(self)?;
        fs::write(caminho, conteudo)
            .context("Falha ao salvar dados")?;
        Ok(())
    }
}

fn criar_projeto(dados: &mut Dados, nome: Option<String>) -> Result<()> {
    let nome = match nome {
        Some(n) => n,
        None => Input::new()
            .with_prompt("Nome do projeto")
            .interact_text()?,
    };

    let descricao: String = Input::new()
        .with_prompt("Descrição")
        .interact_text()?;

    let prioridades = vec!["Alta", "Média", "Baixa"];
    let selecao = Select::new()
        .with_prompt("Prioridade")
        .items(&prioridades)
        .default(1)
        .interact()?;

    let projeto = Projeto {
        id: dados.proximo_id,
        nome: nome.clone(),
        descricao,
        status: "Em andamento".to_string(),
        prioridade: prioridades[selecao].to_string(),
    };

    dados.projetos.push(projeto);
    dados.proximo_id += 1;

    println!(
        "{} Projeto '{}' criado com ID {}",
        "✓".green().bold(),
        nome.cyan(),
        (dados.proximo_id - 1).to_string().yellow()
    );

    Ok(())
}

fn listar_projetos(dados: &Dados, status: Option<String>) {
    let projetos: Vec<&Projeto> = match &status {
        Some(s) => dados.projetos.iter()
            .filter(|p| p.status.to_lowercase().contains(&s.to_lowercase()))
            .collect(),
        None => dados.projetos.iter().collect(),
    };

    if projetos.is_empty() {
        println!("{}", "Nenhum projeto encontrado.".yellow());
        return;
    }

    println!("\n{}", "═══ Projetos ═══".bold().cyan());
    for p in projetos {
        let status_cor = match p.status.as_str() {
            "Concluído" => p.status.green(),
            "Em andamento" => p.status.yellow(),
            _ => p.status.white(),
        };
        let prioridade_cor = match p.prioridade.as_str() {
            "Alta" => p.prioridade.red(),
            "Média" => p.prioridade.yellow(),
            _ => p.prioridade.green(),
        };

        println!(
            "  [{}] {} — {} [{}] [{}]",
            p.id.to_string().bold(),
            p.nome.white().bold(),
            p.descricao.dimmed(),
            status_cor,
            prioridade_cor
        );
    }
    println!();
}

fn concluir_projeto(dados: &mut Dados, id: usize) -> Result<()> {
    let projeto = dados.projetos.iter_mut()
        .find(|p| p.id == id)
        .context(format!("Projeto com ID {} não encontrado", id))?;

    // Simulação de progresso
    let pb = ProgressBar::new(100);
    pb.set_style(
        ProgressStyle::default_bar()
            .template("{spinner:.green} [{bar:40.cyan/blue}] {pos}% {msg}")?
            .progress_chars("█▓░"),
    );
    pb.set_message("Finalizando projeto...");

    for i in 0..100 {
        pb.set_position(i + 1);
        std::thread::sleep(Duration::from_millis(15));
    }
    pb.finish_with_message("Concluído!");

    projeto.status = "Concluído".to_string();
    println!(
        "\n{} Projeto '{}' marcado como concluído!",
        "✓".green().bold(),
        projeto.nome.cyan()
    );

    Ok(())
}

fn remover_projeto(dados: &mut Dados, id: usize) -> Result<()> {
    let nome = dados.projetos.iter()
        .find(|p| p.id == id)
        .map(|p| p.nome.clone())
        .context(format!("Projeto com ID {} não encontrado", id))?;

    let confirmar = Confirm::new()
        .with_prompt(format!("Remover projeto '{}'?", nome))
        .default(false)
        .interact()?;

    if confirmar {
        dados.projetos.retain(|p| p.id != id);
        println!("{} Projeto removido.", "✓".green().bold());
    } else {
        println!("{}", "Operação cancelada.".yellow());
    }

    Ok(())
}

fn gerar_relatorio(dados: &Dados) {
    let total = dados.projetos.len();
    let concluidos = dados.projetos.iter()
        .filter(|p| p.status == "Concluído")
        .count();
    let em_andamento = total - concluidos;

    println!("\n{}", "═══ Relatório ═══".bold().cyan());
    println!("  Total de projetos:  {}", total.to_string().bold());
    println!("  Em andamento:       {}", em_andamento.to_string().yellow());
    println!("  Concluídos:         {}", concluidos.to_string().green());

    if total > 0 {
        let porcentagem = (concluidos as f64 / total as f64) * 100.0;
        let pb = ProgressBar::new(100);
        pb.set_style(
            ProgressStyle::default_bar()
                .template("  Progresso: [{bar:30.green/white}] {pos}%")?
                .progress_chars("█▓░"),
        );
        // unwrap seguro: template é válida (definida acima)
        pb.set_position(porcentagem as u64);
        pb.abandon(); // manter a barra visível
    }
    println!();
}

fn main() -> Result<()> {
    let cli = Cli::parse();
    let mut dados = Dados::carregar(&cli.arquivo)?;

    match cli.comando {
        Comandos::Novo { nome } => criar_projeto(&mut dados, nome)?,
        Comandos::Listar { status } => listar_projetos(&dados, status),
        Comandos::Concluir { id } => concluir_projeto(&mut dados, id)?,
        Comandos::Remover { id } => remover_projeto(&mut dados, id)?,
        Comandos::Relatorio => gerar_relatorio(&dados),
    }

    dados.salvar(&cli.arquivo)?;
    Ok(())
}
```

### Uso da Ferramenta

```bash
# Criar um novo projeto
projetos novo "Reescrever API em Rust"

# Listar todos os projetos
projetos listar

# Filtrar por status
projetos listar --status concluído

# Marcar como concluído
projetos concluir 1

# Gerar relatório
projetos relatorio
```

## CLIs Famosas Escritas em Rust

Rust é responsável por uma nova geração de ferramentas de terminal que substituem utilitários clássicos do Unix com versões mais rápidas e amigáveis:

| Ferramenta | Substitui | Destaque |
|---|---|---|
| **ripgrep (rg)** | grep | 5-10x mais rápido que grep, respeita .gitignore |
| **bat** | cat | Syntax highlighting, integração com Git |
| **eza** | ls | Ícones, cores, tree view integrada |
| **fd** | find | Sintaxe simples, 5x mais rápido, respeita .gitignore |
| **dust** | du | Visualização interativa de uso de disco |
| **delta** | diff | Syntax highlighting para diffs do Git |
| **zoxide** | cd | Navegação inteligente com aprendizado de hábitos |
| **starship** | prompt | Prompt customizável, suporte a 40+ linguagens |
| **bottom (btm)** | top/htop | Monitor de sistema gráfico no terminal |
| **hyperfine** | time | Benchmarking de comandos com estatísticas |

Essas ferramentas demonstram por que Rust é ideal para CLIs: binários pequenos (geralmente 2-10MB), startup em milissegundos, sem dependência de runtime e fácil distribuição.

## Distribuição de Binários

### Cross-compilation

Com `cross` ou `cargo-zigbuild`, você pode compilar para múltiplas plataformas:

```bash
# Instalar cross
cargo install cross

# Compilar para Linux
cross build --release --target x86_64-unknown-linux-musl

# Compilar para macOS
cross build --release --target x86_64-apple-darwin

# Compilar para Windows
cross build --release --target x86_64-pc-windows-gnu
```

### GitHub Actions para Release Automático

```yaml
name: Release
on:
  push:
    tags: ["v*"]

jobs:
  build:
    strategy:
      matrix:
        include:
          - target: x86_64-unknown-linux-musl
            os: ubuntu-latest
          - target: x86_64-apple-darwin
            os: macos-latest
          - target: x86_64-pc-windows-msvc
            os: windows-latest
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}
      - run: cargo build --release --target ${{ matrix.target }}
      - uses: softprops/action-gh-release@v2
        with:
          files: target/${{ matrix.target }}/release/minha-cli*
```

### Publicação no crates.io

```bash
# Login
cargo login

# Publicar
cargo publish
```

Após publicar, qualquer pessoa com Rust instalado pode instalar com `cargo install minha-cli`.

## Empresas Usando Rust para CLIs

- **GitHub**: CLI interna de ferramentas de infraestrutura
- **Mozilla**: Ferramentas de build e empacotamento
- **Vercel**: Turbopack e ferramentas de build (turborepo) contêm componentes Rust
- **Amazon (AWS)**: Ferramentas internas de deploy e monitoramento
- **Astral**: uv (gerenciador de pacotes Python ultrarrápido) e ruff (linter Python) são escritos em Rust

## Como Começar

1. **Fundamentos**: Aprenda Rust com nosso [tutorial de primeiros passos](/tutoriais/primeiros-passos/)
2. **Tratamento de erros**: Essencial para CLIs — veja o [tutorial de tratamento de erros](/tutoriais/tratamento-de-erros/)
3. **Primeira CLI**: Siga o [tutorial de CLI com Clap](/tutoriais/cli-com-clap/)
4. **Leitura de arquivos**: Aprenda a [ler arquivos](/receitas/ler-arquivo/) e [parsear TOML](/receitas/ler-toml/)
5. **Argumentos**: Domine a [receita de argumentos de linha de comando](/receitas/ler-argumentos-cli/)
6. **Distribua**: Configure [GitHub Actions](/instalacao/github-actions/) para releases automáticos

## Conclusão

Rust é, sem dúvida, a melhor linguagem para ferramentas de linha de comando em 2026. A combinação de binários estáticos sem dependências, performance de linguagem compilada, ecossistema maduro de bibliotecas (Clap, indicatif, dialoguer) e facilidade de distribuição cria uma experiência incomparável tanto para desenvolvedores quanto para usuários finais. Se você ainda está escrevendo scripts em Python ou Bash, considere reescrevê-los em Rust — seus usuários vão agradecer.

---

## Veja Também

- [Tutorial: CLI com Clap](/tutoriais/cli-com-clap/) — Crie sua primeira CLI em Rust passo a passo
- [Receita: Ler Argumentos CLI](/receitas/ler-argumentos-cli/) — Como processar argumentos de linha de comando
- [Receita: Ler Input do Usuário](/receitas/ler-input-usuario/) — Leitura interativa no terminal
- [Rust para DevOps](/artigos/rust-para-devops/) — Ferramentas de infraestrutura em Rust
- [Rust para Web](/artigos/rust-para-web/) — Se sua CLI precisa de uma interface web
- [Instalação do Rust](/instalacao/) — Configure seu ambiente de desenvolvimento
- [Empresas que Usam Rust](/empresas/) — Veja quem está contratando

---

Se você desenvolve ferramentas CLI em outras linguagens, confira também:

- <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go para CLIs: construa ferramentas de linha de comando com Cobra e Go</a>
- <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python CLI: crie CLIs rápidas com Click e Typer em Python</a>
