Use Cases (Atualizado: 02/06/2026)

Desenvolvimento Rust com Claude Code: Cargo, ownership, testes e CLI

Guia prático de Rust com Claude Code: Cargo, ownership, testes, fmt, clippy, erros e um CLI pequeno copiável.

Desenvolvimento Rust com Claude Code: Cargo, ownership, testes e CLI

Comece com uma tarefa Rust pequena e verificável

Rust parece difícil no início por causa de ownership, borrowing, lifetimes e tratamento de erro com Result. Uma tradução prática é: ownership define quem possui o dado, borrowing define quem pega emprestado para ler ou modificar, e lifetime define por quanto tempo essa referência é válida. Claude Code ajuda quando trabalha junto com o compilador: explica mensagens, edita arquivos pequenos e executa os comandos de validação.

Neste guia vamos criar um CLI chamado tasknote. Ele lê um tasks.txt com linhas [ ] task e [x] task, mostra um resumo e também lista apenas tarefas abertas. As referências oficiais são Rust Book sobre ownership, Cargo Book para criar projeto, cargo test, rustfmt, Clippy e Claude Code overview.

Se Claude Code ainda é novo para você, leia o guia inicial. Em times, combine com boas práticas de CLAUDE.md e com o guia de permissões.

flowchart LR
  Prompt["Objetivo e limites"]
  Cargo["Projeto Cargo pequeno"]
  Compiler["Ler erros de ownership"]
  Tests["Fixar comportamento"]
  Quality["fmt e clippy"]
  Refactor["Refatorar com diff pequeno"]

  Prompt --> Cargo --> Compiler --> Tests --> Quality --> Refactor

Criar o projeto com Cargo

Cargo é a ferramenta padrão para criar pacotes Rust, compilar, executar, testar e gerenciar dependências. Antes de pedir código ao Claude Code, defina quais comandos provam que a tarefa terminou.

cargo new tasknote --bin
cd tasknote
cargo run

O projeto gerado inclui Cargo.toml e src/main.rs. Projetos atuais podem usar edition = "2024", então peça para Claude Code ler o manifesto real. Neste exemplo usamos só clap para argumentos de linha de comando e anyhow para contexto de erro.

[package]
name = "tasknote"
version = "0.1.0"
edition = "2024"

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

O primeiro prompt deve pedir desenho, não implementação completa.

Neste projeto Cargo, quero criar um CLI `tasknote`.
Ele lê linhas `[ ] task` e `[x] task` em `tasks.txt`.
Primeiro retorne apenas o desenho de `Cargo.toml`, `src/lib.rs` e `src/main.rs`.
Ainda não edite arquivos. Explique as escolhas de ownership e tratamento de erro.

Essa pausa evita código difícil de manter. Em Rust, a pergunta “quem possui este dado?” precisa vir cedo.

Como perguntar sobre ownership e borrowing

No CLI, o conteúdo do arquivo vira String. parse_tasks recebe &str porque só lê o texto. O resultado é Vec<Task>, com cada tarefa possuindo seu título. summarize recebe &[Task] porque apenas percorre a lista.

Explique o ownership de `parse_tasks(input: &str) -> Vec<Task>` e `summarize(tasks: &[Task]) -> String`.
Use somente este CLI como exemplo.
Explique `String`, `&str`, `Vec<Task>` e `&[Task]` para iniciantes.
Não adicione `clone()` desnecessário.

Um erro comum é pedir para “corrigir o borrow checker” e aceitar vários clone(). Clonar pode ser correto, mas precisa de motivo. Se a função só lê dados, borrowing costuma ser melhor.

Exemplo de CLI copiável

Coloque a lógica em src/lib.rs, para testar sem iniciar o CLI.

// src/lib.rs
use anyhow::{Context, Result};
use std::{fs, path::Path};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Task {
    pub title: String,
    pub done: bool,
}

pub fn parse_tasks(input: &str) -> Vec<Task> {
    input.lines().filter_map(parse_task_line).collect()
}

fn parse_task_line(line: &str) -> Option<Task> {
    let trimmed = line.trim();

    if let Some(title) = trimmed
        .strip_prefix("[x] ")
        .or_else(|| trimmed.strip_prefix("[X] "))
    {
        return Some(Task {
            title: title.trim().to_string(),
            done: true,
        });
    }

    if let Some(title) = trimmed.strip_prefix("[ ] ") {
        return Some(Task {
            title: title.trim().to_string(),
            done: false,
        });
    }

    None
}

pub fn summarize(tasks: &[Task]) -> String {
    let total = tasks.len();
    let done = tasks.iter().filter(|task| task.done).count();
    let open = total.saturating_sub(done);

    format!("{total} tasks: {done} done, {open} open")
}

pub fn read_tasks(path: impl AsRef<Path>) -> Result<Vec<Task>> {
    let path = path.as_ref();
    let content = fs::read_to_string(path)
        .with_context(|| format!("failed to read {}", path.display()))?;

    Ok(parse_tasks(&content))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_markdown_style_tasks() {
        let tasks = parse_tasks("[ ] write parser\n[x] add tests\n[X] run clippy\n");

        assert_eq!(
            tasks,
            vec![
                Task {
                    title: "write parser".to_string(),
                    done: false,
                },
                Task {
                    title: "add tests".to_string(),
                    done: true,
                },
                Task {
                    title: "run clippy".to_string(),
                    done: true,
                },
            ]
        );
    }

    #[test]
    fn ignores_unrecognized_lines() {
        let tasks = parse_tasks("# Sprint notes\n- plain bullet\n[ ] keep this\n");

        assert_eq!(tasks.len(), 1);
        assert_eq!(tasks[0].title, "keep this");
    }

    #[test]
    fn summarizes_counts() {
        let tasks = parse_tasks("[ ] one\n[x] two\n[ ] three\n");

        assert_eq!(summarize(&tasks), "3 tasks: 1 done, 2 open");
    }
}

O main.rs fica pequeno.

// src/main.rs
use anyhow::Result;
use clap::Parser;
use std::path::PathBuf;
use tasknote::{read_tasks, summarize};

#[derive(Parser, Debug)]
#[command(name = "tasknote", about = "Summarize simple task files")]
struct Cli {
    #[arg(short, long, default_value = "tasks.txt")]
    file: PathBuf,

    #[arg(long)]
    only_open: bool,
}

fn main() -> Result<()> {
    let args = Cli::parse();
    let tasks = read_tasks(&args.file)?;

    if args.only_open {
        for task in tasks.iter().filter(|task| !task.done) {
            println!("- {}", task.title);
        }
    } else {
        println!("{}", summarize(&tasks));
    }

    Ok(())
}

Exemplo de tasks.txt:

[ ] write parser
[x] add unit tests
[ ] run clippy

Comandos:

cargo fmt
cargo test
cargo clippy --all-targets -- -D warnings
cargo run -- --file tasks.txt
cargo run -- --file tasks.txt --only-open

Como main retorna Result<()>, um arquivo ausente pode mostrar contexto útil em vez de um unwrap() sem explicação. Para CLI, entrada ausente é erro recuperável.

Colocar testes, fmt e Clippy na tarefa

Peça a verificação no mesmo prompt da implementação.

Implemente `src/lib.rs` e `src/main.rs`.
Depois rode `cargo fmt`, `cargo test` e `cargo clippy --all-targets -- -D warnings`.
Se falhar, resuma o erro e corrija com o menor diff razoável.
Não use `unwrap()` fora dos testes.

Claude Code é descrito oficialmente como uma ferramenta agentic que lê código, edita arquivos e executa comandos. Mesmo assim, revise o git diff, confirme o escopo e rode comandos críticos por conta própria quando o risco for alto.

Casos de uso práticos

Primeiro: adicionar uma opção a um CLI existente. Para --json, mantenha summarize estável e crie função separada para formatação.

Segundo: aprender com erros de ownership. Cole o erro completo e peça para explicar quem possui o valor, quem empresta e qual referência vive demais.

Terceiro: correção com testes primeiro. Escreva testes para linha vazia, [X] e linha inválida antes de mudar o parser.

Quarto: refatoração segura. Quando main.rs crescer, separe parsing, I/O e apresentação sem mudar assinaturas públicas.

Armadilhas comuns

Não resolva todos os erros de borrowing com clone(). Pergunte se borrowing basta.

Não deixe unwrap() em caminhos de produção. Arquivos, configuração e entrada de usuário falham.

Não refatore sem testes. Claude Code pode gerar grandes diffs rápido, mas revisão fica difícil.

Não formate um workspace inteiro se a tarefa toca apenas um crate.

Prompt seguro de refatoração

Refatore com segurança o parser de `tasknote`.

Condições:
- Preservar o significado de `[ ] task` e `[x] task`
- Não mudar as assinaturas públicas de `parse_tasks`, `summarize` e `read_tasks`
- Tocar apenas `src/lib.rs` e seus testes
- Não adicionar `clone()` desnecessário
- Primeiro mostrar um plano de 3 linhas e esperar aprovação

Depois:
- `cargo fmt`
- `cargo test`
- `cargo clippy --all-targets -- -D warnings`
- Resumir o diff e explicar decisões de ownership

Esse prompt cria limites, checks e relatório. Rust combina bem com esse método porque compilador, testes e Clippy dão feedback rápido.

CTA e próximos passos

Para praticar, adicione JSON, CSV, varredura de diretórios ou formato com serde. Para times, coloque no CLAUDE.md a edition, comandos obrigatórios, política de unwrap(), crates permitidos e checklist.

ClaudeCodeLab oferece prompts, templates, guias e treinamento para Claude Code. Comece pelo cheatsheet gratuito, veja produtos e templates ou use treinamento e consultoria para adaptar ao seu repositório. Leia também TDD com Claude Code e o checklist de revisão.

Nota de teste: o fluxo mais estável foi manter a lógica em src/lib.rs, fixar comportamento com cargo test e pedir a explicação de ownership antes da edição. Isso reduz clone() desnecessário e deixa o diff menor.

#Claude Code #Rust #CLI #testes #refatoração
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.