Use Cases (Updated: 6/2/2026)

Rust Development With Claude Code: Cargo, Ownership, Tests, and CLI Refactoring

A practical Claude Code Rust guide covering Cargo setup, ownership, tests, fmt, clippy, error handling, and a small CLI.

Rust Development With Claude Code: Cargo, Ownership, Tests, and CLI Refactoring

Start Rust With a Small, Verifiable Task

Rust rewards careful thinking. Its ownership model, borrowing rules, lifetimes, and Result-based error handling can feel strict at first, but the strictness is the feature: the compiler stops many mistakes before they reach production. Claude Code is useful in Rust development when you use it as a pair programmer that reads compiler output, explains tradeoffs, edits focused files, and runs the checks you would run yourself.

This guide builds a tiny CLI named tasknote. It reads a text file containing [ ] task and [x] task lines, then prints a summary or only the open tasks. Along the way we cover Cargo project setup, ownership questions to ask Claude Code, unit tests, cargo fmt, cargo clippy, recoverable errors with Result, and safe refactoring prompts. The primary references are the official Rust Book chapter on ownership, Cargo Book project setup, cargo test reference, rustfmt notes, Clippy usage, and the Claude Code overview.

If you are new to Claude Code itself, read the getting started guide first. For repeatable project rules, combine this article with CLAUDE.md best practices and the permissions guide.

flowchart LR
  Prompt["State the goal and constraints"]
  Cargo["Create a small Cargo crate"]
  Compiler["Read ownership errors"]
  Tests["Lock behavior with tests"]
  Quality["Run fmt and clippy"]
  Refactor["Refactor with narrow diffs"]

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

Create the Cargo Project

Cargo is the standard Rust tool for creating packages, building, running, testing, and managing dependencies. Before asking Claude Code to write a Rust application, decide the verification commands. That gives the agent a clear finish line and keeps the conversation grounded in compiler feedback.

cargo new tasknote --bin
cd tasknote
cargo run

The generated project contains Cargo.toml and src/main.rs. Modern Rust projects may use edition = "2024", so ask Claude Code to read the actual manifest instead of copying an older blog post. For this example, keep dependencies deliberately small: clap for CLI argument parsing and anyhow for error context.

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

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

A good first prompt is not “write the app.” It is a scoped design request:

In this Cargo project, build a small CLI named `tasknote`.
It reads `tasks.txt` lines in the form `[ ] task` and `[x] task`.
First return only the design for `Cargo.toml`, `src/lib.rs`, and `src/main.rs`.
Do not edit files yet. Explain the ownership and error-handling choices.

That pause matters. In Rust, the shape of the data often determines how simple the code will be. Ask whether a function should own a value, borrow it, or return a new value before you let Claude Code edit multiple files.

Ask Ownership and Borrowing Questions

Ownership means one value has one owner. Borrowing means code temporarily reads or mutates a value without taking ownership. Lifetimes describe how long references remain valid. For beginners, translate those terms into plain questions: who owns this data, who only reads it, and how long does the reader need it?

In this CLI, file contents are read into a String, parse_tasks borrows that text as &str, and the parsed result becomes a Vec<Task> that owns each task title. summarize only reads tasks, so it takes &[Task]. This is a useful pattern to ask Claude Code to explain:

Explain the ownership in `parse_tasks(input: &str) -> Vec<Task>` and `summarize(tasks: &[Task]) -> String`.
Use this CLI as the only example.
Explain `String`, `&str`, `Vec<Task>`, and `&[Task]` in beginner-friendly language.
Avoid adding unnecessary `clone()` calls.

The common beginner mistake is asking Claude Code to “fix the borrow checker” and accepting a patch full of clone(). Cloning is sometimes correct, but it should be intentional. If the function only reads data, borrowing usually communicates the design better.

Copy-Paste CLI Example

Put the core logic in src/lib.rs so it can be tested without running the CLI. Keeping main.rs thin also makes future refactors easier to review.

// 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");
    }
}

Now wire it into src/main.rs.

// 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(())
}

Create a tasks.txt file like this:

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

Then verify the crate:

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

The main function returns Result<()>, so file errors can carry context instead of crashing with unwrap(). The Rust Book’s error-handling chapter distinguishes recoverable errors, such as a missing file, from unrecoverable bugs. For a CLI, missing input is recoverable: show the user what failed and let them fix the path.

Put Tests, fmt, and Clippy in the Task

Claude Code should not stop after producing code. Ask it to run the same commands you expect in a pull request.

Implement `src/lib.rs` and `src/main.rs`.
After editing, run `cargo fmt`, `cargo test`, and `cargo clippy --all-targets -- -D warnings`.
If a command fails, summarize the error first, then fix it with the smallest reasonable diff.
Do not use `unwrap()` outside tests.

This turns the compiler, tests, and Clippy into part of the workflow. Claude Code’s official docs describe it as an agentic coding tool that can read a codebase, edit files, and run commands. That does not remove your review obligation. Always inspect git diff, check whether the touched files match the request, and rerun important commands yourself when the change is risky.

Practical Use Cases

The first use case is adding a small feature to an existing Rust CLI. If you need --json output, tell Claude Code to keep summarize stable and add JSON formatting as a separate function. That keeps tests meaningful and prevents a simple flag from turning into a rewrite.

The second use case is learning from ownership errors. When you see messages like cannot move out of or borrowed value does not live long enough, paste the full error and ask Claude Code to explain who owns the value, who borrows it, and which reference lives too long. The goal is not just a patch; it is a mental model.

The third use case is test-first bug fixing. Add tests for blank lines, uppercase [X], and ignored notes before changing the parser. Rust’s type system catches many mistakes, but tests capture your product rules.

The fourth use case is safe refactoring. When main.rs grows, ask Claude Code to move parsing, output, and file I/O into separate modules without changing public function signatures. Small moves with passing tests are easier to review than one large “cleanup.”

Pitfalls to Avoid

Do not silence the borrow checker with clones everywhere. Ask whether the data can be borrowed and where ownership should live. A clone() with a reason is fine; a clone() added only to make the compiler stop complaining should be reviewed.

Do not leave unwrap() in production CLI paths. It hides useful context from users. Use Result, ?, and contextual messages for file I/O and parsing.

Do not refactor without tests. Claude Code can make wide changes quickly, but wide diffs are hard to trust. Lock behavior with cargo test, then let the agent edit.

Do not let formatting touch unrelated crates in a busy workspace. In a shared repository, tell Claude Code which package and files are in scope. If other people or agents are working, narrow the command and report the touched paths.

Safe Refactoring Prompt

Use prompts that define scope, preserved behavior, allowed files, and verification.

Safely refactor the `tasknote` parser.

Constraints:
- Preserve the meaning of `[ ] task` and `[x] task`
- Do not change the public signatures of `parse_tasks`, `summarize`, or `read_tasks`
- Touch only `src/lib.rs` and its tests
- Do not add unnecessary `clone()` calls
- First return a 3-line plan and wait for approval

After editing:
- Run `cargo fmt`
- Run `cargo test`
- Run `cargo clippy --all-targets -- -D warnings`
- Summarize the diff and explain any ownership decisions

This is the practical meaning of giving the agent a harness: the task has boundaries, checks, and reporting requirements. Rust development works especially well with that style because the compiler, tests, and linter can all push back on low-quality changes.

Monetization and Next Steps

For personal practice, extend this CLI with JSON output, CSV export, directory scanning, or a serde-based file format. For a team, document the Rust edition, required commands, unwrap() policy, allowed crates, and review checklist in CLAUDE.md.

ClaudeCodeLab packages these workflows into prompts, setup guides, and team training. Start with the free Claude Code cheatsheet if you need command reminders. Use the products and templates for reusable prompts and CLAUDE.md material. For repository-specific rollout, use Claude Code training and consultation. Related follow-ups: TDD with Claude Code and the review workflow checklist.

Tested-result note: this structure is the most reliable when the logic starts in src/lib.rs, behavior is fixed with cargo test, and Claude Code is asked to explain ownership before editing. In practice, that produces fewer unnecessary clones and smaller reviewable diffs than asking for a full CLI in one broad prompt.

#Claude Code #Rust #CLI #testing #refactoring
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.