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.
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.
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.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.