Use Cases (更新: 2026/6/2)

用 Claude Code 做 Rust 开发:Cargo、所有权、测试与 CLI 重构

面向初学者的 Claude Code Rust 实战:Cargo、所有权、测试、fmt、clippy、错误处理和小型 CLI。

用 Claude Code 做 Rust 开发:Cargo、所有权、测试与 CLI 重构

先把任务缩小到可验证的 Rust 项目

Rust 的学习曲线主要来自所有权、借用、生命周期和 Result 错误处理。Claude Code 可以帮你读代码、改文件、运行命令,但在 Rust 项目里,最好的用法不是“把需求全部扔给 AI”,而是让它和编译器一起工作:先解释设计,再编辑小范围文件,最后用测试、格式化和 Clippy 验证。

本文用一个小型 CLI tasknote 作为练习。它读取 tasks.txt 中的 [ ] task[x] task 行,输出完成数和未完成数,也可以只列出未完成任务。参考资料以官方文档为准:Rust Book 所有权章节Cargo Book 创建项目cargo testrustfmtClippyClaude Code overview

如果你还不熟悉 Claude Code,可以先看入门指南。团队使用时,把命令、依赖策略和禁止事项写进 CLAUDE.md 最佳实践,再配合权限设置指南

flowchart LR
  Prompt["说明目标和约束"]
  Cargo["用 Cargo 创建小项目"]
  Compiler["阅读所有权错误"]
  Tests["用测试固定行为"]
  Quality["运行 fmt 和 clippy"]
  Refactor["小差异安全重构"]

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

用 Cargo 建立最小项目

Cargo 是 Rust 的标准项目工具,负责创建包、构建、运行、测试和管理依赖。先确定验证命令,再让 Claude Code 写代码,这样任务会更清晰。

cargo new tasknote --bin
cd tasknote
cargo run

这个命令会生成 Cargo.tomlsrc/main.rs。现代 Rust 项目可能使用 edition = "2024",所以不要让 Claude Code 复制旧文章里的 edition;让它读取当前项目的 Cargo.toml。这个例子只需要两个依赖:clap 用于命令行参数,anyhow 用于给错误增加上下文。

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

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

推荐先用设计提示,而不是直接要求生成全部代码。

在这个 Cargo 项目里实现一个小型 CLI `tasknote`。
它读取 `tasks.txt`,行格式为 `[ ] task` 和 `[x] task`。
请先只给出 `Cargo.toml`、`src/lib.rs`、`src/main.rs` 的设计方案。
暂时不要编辑文件。请说明所有权、借用和错误处理的选择。

这个停顿很重要。Rust 的代码是否简单,往往取决于一开始是否分清“谁拥有数据”和“谁只是读取数据”。

让 Claude Code 解释所有权和借用

所有权可以理解为“这个值由谁负责”。借用是“临时读取或修改,但不拿走所有权”。生命周期是“这个引用能活多久”。初学时不要只背术语,要把问题改写成:这个字符串应该由谁持有?这个函数只是读取,还是要返回新数据?

tasknote 中,文件内容先读成 Stringparse_tasks 只借用文本,所以参数是 &str;解析结果是 Vec<Task>,每个任务标题由 Task 自己持有;summarize 只读任务列表,所以参数是 &[Task]

请解释 `parse_tasks(input: &str) -> Vec<Task>` 和 `summarize(tasks: &[Task]) -> String` 的所有权设计。
只使用这个 CLI 的例子,不要泛泛而谈。
请用初学者能懂的中文解释 `String`、`&str`、`Vec<Task>`、`&[Task]`。
不要为了消除借用错误而随意增加 `clone()`。

常见失败是把所有借用错误都用 clone() 压下去。clone() 不是禁忌,但必须有理由。如果函数只是读取数据,借用通常更准确,也更容易被后续维护者理解。

可复制运行的小型 CLI

把核心逻辑放进 src/lib.rs,这样不用启动命令行也能测试。main.rs 越薄,之后让 Claude Code 重构时差异越容易审核。

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

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

示例 tasks.txt

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

验证命令:

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

这里 main 返回 Result<()>。如果文件不存在,anyhow::Context 会显示哪个路径读取失败,而不是用 unwrap() 直接崩溃。对于 CLI,缺少输入文件是可恢复错误,应该给用户可操作的信息。

把测试、fmt、Clippy 写进任务要求

Claude Code 完成代码后,必须运行验证命令。cargo test 用于单元测试、集成测试和文档测试;cargo fmt 统一风格;cargo clippy 发现常见问题。可以这样要求:

请实现 `src/lib.rs` 和 `src/main.rs`。
编辑后运行 `cargo fmt`、`cargo test`、`cargo clippy --all-targets -- -D warnings`。
如果失败,先总结错误,再用最小差异修复。
除测试外不要使用 `unwrap()`。

Claude Code 官方文档把它描述为能读代码库、编辑文件、运行命令的 agentic coding tool。即便如此,你仍然要检查 git diff、确认触碰文件是否在范围内,并在风险较高时自己重新运行命令。

三个以上的实际用例

第一个用例是给现有 Rust CLI 增加小功能。例如添加 --json 输出时,让 Claude Code 保持 summarize 不变,把 JSON 格式化写成新函数,避免简单功能变成大重写。

第二个用例是学习所有权错误。遇到 cannot move out ofborrowed value does not live long enough 时,把完整错误、相关函数和你期望的数据生命周期交给 Claude Code,让它解释谁拥有值、谁借用值。

第三个用例是测试先行修 bug。先为大小写 [X]、空行、不合法行写测试,再改解析器。Rust 的类型系统很强,但业务规则仍然需要测试固定。

第四个用例是安全重构。当 main.rs 变厚时,请 Claude Code 把解析、文件 I/O、显示逻辑拆开,同时保持公开函数签名不变。

常见陷阱

不要用大量 clone() 掩盖设计问题。每次看到新 clone(),都问一句:这里真的需要拥有数据吗?

不要在生产 CLI 路径留下 unwrap()。文件、配置、网络输入都会失败,应该使用 Result 和上下文信息。

不要在没有测试的情况下重构。Claude Code 改得很快,但大差异很难审。先固定行为,再让它移动代码。

不要在共享 workspace 中随意运行全局格式化。多人或多 agent 同时工作时,明确包名、文件范围和允许的命令。

安全重构提示词

请安全重构 `tasknote` 的解析器。

约束:
- 保持 `[ ] task` 与 `[x] task` 的语义不变
- 不改变 `parse_tasks`、`summarize`、`read_tasks` 的公开签名
- 只触碰 `src/lib.rs` 和其中的测试
- 不增加没有必要的 `clone()`
- 先给出 3 行计划,等待确认后再编辑

编辑后:
- 运行 `cargo fmt`
- 运行 `cargo test`
- 运行 `cargo clippy --all-targets -- -D warnings`
- 总结差异,并说明所有权相关判断

这种写法给了 Claude Code 明确的工作边界、检查命令和报告要求。Rust 项目尤其适合这样做,因为编译器、测试和 linter 都会对低质量修改进行反馈。

变现导线与后续学习

个人练习可以继续扩展 JSON 输出、CSV 导出、目录扫描或 serde 文件格式。团队使用时,请把 Rust edition、必跑命令、unwrap() 策略、允许依赖和评审清单写进 CLAUDE.md

ClaudeCodeLab 提供 Claude Code 提示词、设置指南、CLAUDE.md 模板和团队导入咨询。想先免费熟悉命令,可以看免费速查表;需要模板和产品化资料,可以查看产品与模板;如果要把 Rust 开发流程放进真实仓库,请看培训与咨询。后续也可以阅读用 Claude Code 做 TDD评审流程清单

实测经验:先把逻辑放进 src/lib.rs,用 cargo test 固定行为,再让 Claude Code 解释所有权并编辑,得到的差异最容易审核。直接要求“一次写完整 CLI”往往会产生更多不必要的 clone() 和更大的修改范围。

#Claude Code #Rust #CLI #测试 #重构
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。