<!-- title: Claude Code Rust开发：编译器即队友 -->

# Claude Code + Rust 开发全链路实战：当编译器成为最强队友

## 概述

本教程面向有一定 Rust 基础、希望用 Claude Code 提升 Rust 开发效率的开发者。你将学会：配置 Rust 项目专属的 CLAUDE.md，利用 Claude Code 解决所有权/借用检查器错误，搭建 Axum 异步 Web 服务，掌握 Criterion 性能基准测试，以及处理 FFI/unsafe 边界代码。全程以一个**可运行的 URL 短链服务**为实战案例，从零到部署一气呵成。

**适合人群**：掌握 Rust 基础语法，已安装 Claude Code CLI，希望在日常 Rust 项目中融入 AI 辅助开发流程的工程师。

---

## 前置条件

| 工具 | 版本要求 | 安装命令 |
|------|---------|---------|
| Rust toolchain | 1.85+ | `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \| sh` |
| Claude Code CLI | 最新版 | `npm install -g @anthropic-ai/claude-code` |
| cargo-nextest | 可选 | `cargo install cargo-nextest` |
| cargo-criterion | 可选 | `cargo install cargo-criterion` |
| flamegraph | 可选 | `cargo install flamegraph` |

确认环境就绪：

```bash
rustc --version     # 期望 >= 1.85.0
claude --version    # 期望最新版
cargo clippy --version  # 期望随 Rust 一起安装
```

---

## 第一步：为什么 Rust 是 Claude Code 的最佳搭档

Rust 有一个被广泛低估的特性——**编译器作为持续审查者**。与 Python、JavaScript 等动态语言不同，Rust 的类型系统和借用检查器会在编译阶段拦截大量错误。这意味着 Claude Code 生成的代码必须通过编译器这一"铁面判官"，而编译器的错误信息本身就构成了高质量的反馈回路。

Tigran Hovhannisyan 在其实践中发现：**"Rust 编译器本质上就是一位高级工程师"**——它在编译时捕获的问题，恰好是 AI 编码工具最易犯错的地方：空指针、数据竞争、资源泄漏。这种严格性让 Claude Code 在无人监督模式下完成大型任务的可靠性大幅提升。

### Rust + Claude Code 的协同增益矩阵

```
┌─────────────────┬──────────────┬──────────────────────────────┐
│  开发阶段        │  Rust 痛点    │  Claude Code 增益             │
├─────────────────┼──────────────┼──────────────────────────────┤
│  编码            │  所有权语法复杂 │  自动生成符合借用规则的代码    │
│  编译            │  错误信息冗长  │  即时解读错误、给出修复建议    │
│  测试            │  测试样板多   │  自动生成单元/集成测试         │
│  重构            │  影响范围难追踪 │  利用类型系统精确定位改动点    │
│  性能调优        │  需要深入理解  │  生成 benchmark + 分析建议    │
│  FFI/unsafe     │  安全边界模糊  │  生成安全封装、标注不变量      │
└─────────────────┴──────────────┴──────────────────────────────┘
```

---

## 第二步：配置 Rust 项目专属 CLAUDE.md

一个精心设计的 CLAUDE.md 可以将 Claude Code 的 Rust 编码准确率提升 40% 以上。以下是经过实战验证的配置模板：

### 2.1 项目根目录 CLAUDE.md

```markdown
# Rust URL Shortener - Project Instructions

## Build & Test Commands
- 构建：`cargo build`
- 测试：`cargo nextest run`（优先）或 `cargo test`
- 单个测试：`cargo test test_name -- --nocapture`
- Lint：`cargo clippy -- -D warnings`
- 格式化：`cargo fmt --check`
- 基准测试：`cargo criterion`

## Architecture
- 使用 Axum 0.8 + Tokio 1.x 异步运行时
- 数据层：SQLx + SQLite（开发）/ PostgreSQL（生产）
- 缓存层：moka 异步缓存
- 所有 handler 函数签名必须显式标注返回类型

## Code Style
- 错误处理统一使用 thiserror + anyhow（库用 thiserror，应用用 anyhow）
- 禁止 unwrap()，测试代码除外
- 公共 API 必须有 doc comment（/// 风格）
- 使用 `#[expect(dead_code)]` 而非 `#[allow(dead_code)]`
- 优先使用 `impl Into<String>` 而非直接接收 `String`

## Ownership Rules
- 遇到借用冲突，优先考虑：克隆 < 重构生命周期 < Arc<Mutex<T>>
- 大于 128 字节的结构体优先用 Arc 共享所有权
- 不要为了绕过借用检查器盲目 clone

## Forbidden Patterns
- 禁止在 async 函数中使用 std::sync::Mutex（必须用 tokio::sync::Mutex）
- 禁止在循环中创建大量临时 String（使用 &str + lifetime）
- 禁止忽略 clippy warning
```

### 2.2 常见问题与解决方案

**Q：CLAUDE.md 应该放多少内容？**

A：遵循"一屏原则"——控制在 80 行以内。过长的 CLAUDE.md 会消耗上下文窗口，反而降低效果。把详细架构说明放到 `docs/architecture.md`，CLAUDE.md 中只引用路径。

**Q：workspace 项目怎么配置？**

A：根 CLAUDE.md 放通用规则，每个 crate 的子目录放专属 CLAUDE.md。Claude Code 会自动合并层级配置。

---

## 第三步：项目初始化与依赖管理

### 3.1 创建项目

```bash
# 创建项目结构
mkdir rust-url-shortener && cd rust-url-shortener
cargo init --name url-shortener

# 创建模块目录
mkdir -p src/{handlers,models,storage,cache}
```

### 3.2 添加核心依赖

向 Claude Code 提示：

```
请为这个 URL 短链服务添加 Cargo.toml 依赖，需要：
1. axum 0.8 + tokio（异步 Web 框架）
2. sqlx + SQLite（持久化）
3. serde + serde_json（序列化）
4. moka（异步缓存）
5. nanoid（短链 ID 生成）
6. thiserror + anyhow（错误处理）
7. tracing + tracing-subscriber（日志）
8. criterion（基准测试，dev-dependency）
```

Claude Code 生成的 `Cargo.toml`：

```toml
[package]
name = "url-shortener"
version = "0.1.0"
edition = "2024"

[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
moka = { version = "0.12", features = ["future"] }
nanoid = "0.4"
thiserror = "2"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
axum-test = "16"
```

### 3.3 预期结果

```bash
$ cargo check
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.3s
```

---

## 第四步：核心模块开发——用 Claude Code 对抗借用检查器

### 4.1 错误模型定义

让 Claude Code 生成错误类型：

```
请为 URL 短链服务定义统一的错误类型，使用 thiserror。
需要覆盖：无效 URL、ID 冲突、数据库错误、缓存错误、ID 未找到。
同时实现 Into<axum::response::Response> 以直接作为 handler 返回值。
```

Claude Code 生成的 `src/models/errors.rs`：

```rust
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("无效的 URL: {0}")]
    InvalidUrl(String),

    #[error("短链 ID 已存在: {0}")]
    IdConflict(String),

    #[error("未找到短链: {0}")]
    NotFound(String),

    #[error("数据库错误: {0}")]
    Database(#[from] sqlx::Error),

    #[error("缓存错误: {0}")]
    Cache(String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match &self {
            AppError::InvalidUrl(_) => (StatusCode::BAD_REQUEST, self.to_string()),
            AppError::IdConflict(_) => (StatusCode::CONFLICT, self.to_string()),
            AppError::NotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
            AppError::Database(_) => {
                tracing::error!("数据库错误: {:?}", self);
                (StatusCode::INTERNAL_SERVER_ERROR, "内部错误".to_string())
            }
            AppError::Cache(_) => {
                tracing::warn!("缓存降级: {:?}", self);
                // 缓存失败不应阻断请求，但这里作为错误类型保留
                (StatusCode::INTERNAL_SERVER_ERROR, "内部错误".to_string())
            }
        };
        (status, message).into_response()
    }
}
```

### 4.2 数据模型与借用检查器实战

这是 Claude Code + Rust 的**核心战场**。让我们设计存储层：

```
请实现一个 URL 存储层，要求：
1. 使用 sqlx 的 SQLite 连接池
2. 提供 create_short_url、get_original_url、delete_url 方法
3. 所有方法都是 async 的
4. 使用 Arc<SqlitePool> 共享连接池
5. 错误传播使用 AppError
```

Claude Code 生成的 `src/storage/url_store.rs`：

```rust
use std::sync::Arc;

use sqlx::SqlitePool;

use crate::models::errors::AppError;

#[derive(Debug, Clone)]
pub struct UrlRecord {
    pub id: String,
    pub original_url: String,
    pub created_at: chrono::NaiveDateTime,
    pub hit_count: i64,
}

#[derive(Clone)]
pub struct UrlStore {
    pool: Arc<SqlitePool>,
}

impl UrlStore {
    pub fn new(pool: SqlitePool) -> Self {
        Self {
            pool: Arc::new(pool),
        }
    }

    pub async fn init_schema(&self) -> Result<(), AppError> {
        sqlx::query(
            r#"
            CREATE TABLE IF NOT EXISTS urls (
                id TEXT PRIMARY KEY,
                original_url TEXT NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                hit_count INTEGER DEFAULT 0
            )
            "#,
        )
        .execute(&*self.pool)
        .await?;
        Ok(())
    }

    pub async fn create_short_url(
        &self,
        id: &str,
        original_url: &str,
    ) -> Result<UrlRecord, AppError> {
        let record = sqlx::query_as!(
            UrlRecord,
            r#"
            INSERT INTO urls (id, original_url)
            VALUES (?, ?)
            RETURNING id, original_url, created_at, hit_count
            "#,
            id,
            original_url
        )
        .fetch_one(&*self.pool)
        .await?;
        Ok(record)
    }

    pub async fn get_original_url(&self, id: &str) -> Result<UrlRecord, AppError> {
        let record = sqlx::query_as!(
            UrlRecord,
            r#"
            UPDATE urls SET hit_count = hit_count + 1
            WHERE id = ?
            RETURNING id, original_url, created_at, hit_count
            "#,
            id
        )
        .fetch_optional(&*self.pool)
        .await?
        .ok_or_else(|| AppError::NotFound(id.to_string()))?;
        Ok(record)
    }

    pub async fn delete_url(&self, id: &str) -> Result<(), AppError> {
        let result = sqlx::query("DELETE FROM urls WHERE id = ?")
            .bind(id)
            .execute(&*self.pool)
            .await?;
        if result.rows_affected() == 0 {
            return Err(AppError::NotFound(id.to_string()));
        }
        Ok(())
    }
}
```

### 4.3 借用冲突的典型场景与 Claude Code 解法

在缓存层集成中，你几乎必然遇到借用冲突。让我们故意写一段有问题的代码，然后让 Claude Code 修复：

**有问题的代码**：

```rust
// 编译会报错：cannot borrow `self.cache` as mutable more than once
pub async fn get_or_fetch(&mut self, id: &str) -> Result<UrlRecord, AppError> {
    if let Some(cached) = self.cache.get(id) {
        return Ok(cached);
    }
    let record = self.store.get_original_url(id).await?;
    self.cache.insert(id.to_string(), record.clone()).await;
    Ok(record)
}
```

**向 Claude Code 提示**：

```
上面的 get_or_fetch 方法有借用冲突。请重构：
1. 使用 moka 的异步缓存 API（不需要 &mut self）
2. 让缓存层持有 UrlStore 的 Arc clone
3. 缓存未命中时自动回源到 store
```

**Claude Code 的修复方案**：

```rust
use std::sync::Arc;

use moka::future::Cache;

use crate::models::errors::AppError;
use crate::storage::url_store::{UrlRecord, UrlStore};

#[derive(Clone)]
pub struct CacheLayer {
    cache: Cache<String, UrlRecord>,
    store: UrlStore, // UrlStore 内部已经是 Arc<SqlitePool>，Clone 成本极低
}

impl CacheLayer {
    pub fn new(store: UrlStore, max_capacity: u64) -> Self {
        let cache = Cache::builder()
            .max_capacity(max_capacity)
            .time_to_idle(std::time::Duration::from_secs(3600))
            .build();
        Self { cache, store }
    }

    pub async fn get_or_fetch(&self, id: &str) -> Result<UrlRecord, AppError> {
        // moka Cache 的 get 是 &self，无需可变借用
        if let Some(cached) = self.cache.get(id).await {
            tracing::debug!(id, "缓存命中");
            return Ok(cached);
        }

        tracing::debug!(id, "缓存未命中，回源查询");
        let record = self.store.get_original_url(id).await?;
        self.cache.insert(id.to_string(), record.clone()).await;
        Ok(record)
    }

    pub async fn invalidate(&self, id: &str) {
        self.cache.invalidate(id).await;
    }
}
```

**关键洞察**：Claude Code 没有简单地添加 `Arc<Mutex<...>>` 来绕过问题，而是利用了 moka 的 `Cache` 本身就是 `&self` 安全的并发结构这一特性。这是**地道（idiomatic）Rust**的体现。

---

## 第五步：Axum Handler 开发与类型驱动路由

### 5.1 应用状态设计

```rust
// src/app_state.rs
use std::sync::Arc;

use crate::cache::CacheLayer;
use crate::storage::UrlStore;

#[derive(Clone)]
pub struct AppState {
    pub store: UrlStore,
    pub cache: CacheLayer,
    pub id_generator: Arc<dyn IdGenerator + Send + Sync>,
}

pub trait IdGenerator {
    fn generate(&self) -> String;
}

pub struct NanoidGenerator {
    alphabet: Vec<char>,
    length: usize,
}

impl NanoidGenerator {
    pub fn new(length: usize) -> Self {
        // 使用 URL 安全的字符集
        let alphabet: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
            .chars()
            .collect();
        Self { alphabet, length }
    }
}

impl IdGenerator for NanoidGenerator {
    fn generate(&self) -> String {
        nanoid::nanoid!(self.length, &self.alphabet)
    }
}
```

### 5.2 Handler 实现

让 Claude Code 一次性生成完整的 CRUD handler：

```
请为 URL 短链服务实现 Axum 0.8 handler，包含：
1. POST /shorten - 创建短链，接收 JSON { "url": "..." }
2. GET /:id - 302 重定向到原始 URL
3. DELETE /:id - 删除短链
4. GET /:id/stats - 查看统计信息
使用 State extractor 共享 AppState。
```

```rust
// src/handlers/url_handler.rs
use axum::{
    extract::{Path, State},
    http::StatusCode,
    response::{Html, IntoResponse, Redirect},
    Json,
};
use serde::{Deserialize, Serialize};

use crate::app_state::AppState;
use crate::models::errors::AppError;

#[derive(Debug, Deserialize)]
pub struct ShortenRequest {
    pub url: String,
}

#[derive(Debug, Serialize)]
pub struct ShortenResponse {
    pub id: String,
    pub short_url: String,
}

#[derive(Debug, Serialize)]
pub struct StatsResponse {
    pub id: String,
    pub original_url: String,
    pub hit_count: i64,
    pub created_at: String,
}

pub async fn shorten(
    State(state): State<AppState>,
    Json(body): Json<ShortenRequest>,
) -> Result<(StatusCode, Json<ShortenResponse>), AppError> {
    // 验证 URL
    if !body.url.starts_with("http://") && !body.url.starts_with("https://") {
        return Err(AppError::InvalidUrl(body.url));
    }

    let id = state.id_generator.generate();
    let record = state.store.create_short_url(&id, &body.url).await?;

    Ok((
        StatusCode::CREATED,
        Json(ShortenResponse {
            id: record.id,
            short_url: format!("http://localhost:3000/{}", id),
        }),
    ))
}

pub async fn redirect(
    State(state): State<AppState>,
    Path(id): Path<String>,
) -> Result<Redirect, AppError> {
    let record = state.cache.get_or_fetch(&id).await?;
    Ok(Redirect::permanent(&record.original_url))
}

pub async fn delete(
    State(state): State<AppState>,
    Path(id): Path<String>,
) -> Result<StatusCode, AppError> {
    state.store.delete_url(&id).await?;
    state.cache.invalidate(&id).await;
    Ok(StatusCode::NO_CONTENT)
}

pub async fn stats(
    State(state): State<AppState>,
    Path(id): Path<String>,
) -> Result<Json<StatsResponse>, AppError> {
    // stats 不走缓存，直接查库获取最新 hit_count
    let record = state.store.get_original_url(&id).await?;
    Ok(Json(StatsResponse {
        id: record.id,
        original_url: record.original_url,
        hit_count: record.hit_count,
        created_at: record.created_at.to_string(),
    }))
}
```

### 5.3 路由组装

```rust
// src/main.rs
use axum::{routing::{delete, get, post}, Router};
use sqlx::SqlitePool;
use tracing_subscriber::EnvFilter;

mod app_state;
mod cache;
mod handlers;
mod models;
mod storage;

use app_state::{AppState, NanoidGenerator};
use cache::CacheLayer;
use handlers::url_handler;
use storage::UrlStore;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env().add_directive("url_shortener=debug".parse()?))
        .init();

    let pool = SqlitePool::connect("sqlite:url_shortener.db").await?;
    let store = UrlStore::new(pool);
    store.init_schema().await?;

    let cache = CacheLayer::new(store.clone(), 10_000);
    let state = AppState {
        store,
        cache,
        id_generator: std::sync::Arc::new(NanoidGenerator::new(7)),
    };

    let app = Router::new()
        .route("/shorten", post(url_handler::shorten))
        .route("/{id}", get(url_handler::redirect))
        .route("/{id}", delete(url_handler::delete))
        .route("/{id}/stats", get(url_handler::stats))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
    tracing::info!("服务启动: http://localhost:3000");
    axum::serve(listener, app).await?;
    Ok(())
}
```

---

## 第六步：测试策略——Rust 类型系统 + Claude Code 的双重保障

### 6.1 单元测试

让 Claude Code 生成测试时，使用以下 prompt 模板：

```
请为 src/cache.rs 的 CacheLayer 编写单元测试，要求：
1. 使用 #[tokio::test] 异步测试
2. 测试场景：缓存命中、缓存未命中回源、缓存失效
3. 使用 in-memory SQLite（:memory:）避免文件依赖
4. 不要使用 unwrap()，用 ? 传播错误
5. 测试函数名使用 snake_case 描述行为
```

Claude Code 生成的测试：

```rust
#[cfg(test)]
mod tests {
    use super::*;
    use crate::storage::UrlStore;
    use sqlx::SqlitePool;

    async fn setup_test_store() -> UrlStore {
        let pool = SqlitePool::connect("sqlite::memory:")
            .await
            .expect("创建内存数据库失败");
        let store = UrlStore::new(pool);
        store
            .init_schema()
            .await
            .expect("初始化 schema 失败");
        store
    }

    #[tokio::test]
    async fn cache_miss_triggers_store_fetch() -> anyhow::Result<()> {
        let store = setup_test_store().await;
        store.create_short_url("abc1234", "https://example.com").await?;

        let cache = CacheLayer::new(store.clone(), 100);
        let record = cache.get_or_fetch("abc1234").await?;

        assert_eq!(record.original_url, "https://example.com");
        Ok(())
    }

    #[tokio::test]
    async fn cache_hit_returns_cached_value() -> anyhow::Result<()> {
        let store = setup_test_store().await;
        store.create_short_url("xyz9999", "https://rust-lang.org").await?;

        let cache = CacheLayer::new(store.clone(), 100);
        // 第一次查询：缓存未命中
        let _ = cache.get_or_fetch("xyz9999").await?;
        // 第二次查询：缓存命中
        let record = cache.get_or_fetch("xyz9999").await?;
        assert_eq!(record.original_url, "https://rust-lang.org");
        Ok(())
    }

    #[tokio::test]
    async fn cache_invalidate_removes_entry() -> anyhow::Result<()> {
        let store = setup_test_store().await;
        store.create_short_url("del001", "https://test.com").await?;

        let cache = CacheLayer::new(store.clone(), 100);
        let _ = cache.get_or_fetch("del001").await?;
        cache.invalidate("del001").await;

        // 缓存已失效，但 store 中数据仍在
        let record = store.get_original_url("del001").await?;
        assert_eq!(record.hit_count, 1); // 之前查询过一次
        Ok(())
    }
}
```

### 6.2 集成测试（使用 axum-test）

```rust
// tests/integration_test.rs
use axum_test::TestServer;
use sqlx::SqlitePool;

async fn setup_server() -> anyhow::Result<TestServer> {
    // 复用 main.rs 中的 app 构建，但使用内存数据库
    let pool = SqlitePool::connect("sqlite::memory:").await?;
    let store = url_shortener::storage::UrlStore::new(pool);
    store.init_schema().await?;

    let cache = url_shortener::cache::CacheLayer::new(store.clone(), 100);
    let state = url_shortener::app_state::AppState {
        store,
        cache,
        id_generator: std::sync::Arc::new(
            url_shortener::app_state::NanoidGenerator::new(7),
        ),
    };

    let app = url_shortener::create_app(state);
    Ok(TestServer::new(app)?)
}

#[tokio::test]
async fn full_shorten_redirect_flow() -> anyhow::Result<()> {
    let server = setup_server().await?;

    // 创建短链
    let response = server
        .post("/shorten")
        .json(&serde_json::json!({ "url": "https://doc.rust-lang.org" }))
        .await;
    response.assert_status(axum_test::http::StatusCode::CREATED);

    let body: serde_json::Value = response.json();
    let id = body["id"].as_str().unwrap();

    // 访问短链应重定向
    let response = server.get(&format!("/{}", id)).await;
    response.assert_status(axum_test::http::StatusCode::PERMANENT_REDIRECT);

    // 查看统计
    let response = server.get(&format!("/{}/stats", id)).await;
    response.assert_status(axum_test::http::StatusCode::OK);
    let stats: serde_json::Value = response.json();
    assert_eq!(stats["hit_count"], 1);

    // 删除
    let response = server.delete(&format!("/{}", id)).await;
    response.assert_status(axum_test::http::StatusCode::NO_CONTENT);

    // 再次访问应 404
    let response = server.get(&format!("/{}", id)).await;
    response.assert_status(axum_test::http::StatusCode::NOT_FOUND);

    Ok(())
}
```

### 6.3 运行全部测试

```bash
# 运行单元 + 集成测试
cargo nextest run

# 同时运行 clippy 检查
cargo clippy -- -D warnings

# 格式检查
cargo fmt --check
```

---

## 第七步：性能基准测试与 Profiling

### 7.1 Criterion 基准测试

```rust
// benches/url_operations.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use url_shortener::storage::UrlStore;
use sqlx::SqlitePool;

fn bench_create_short_url(c: &mut Criterion) {
    let rt = tokio::runtime::Runtime::new().unwrap();
    let pool = rt.block_on(async {
        let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
        let store = UrlStore::new(pool.clone());
        store.init_schema().await.unwrap();
        pool
    });

    let mut group = c.benchmark_group("url_operations");
    group.sample_size(100);

    group.bench_function("create_short_url", |b| {
        b.to_async(&rt).iter(|| async {
            let store = UrlStore::new(pool.clone());
            let id = nanoid::nanoid!(7);
            store.create_short_url(&id, "https://example.com/very/long/path").await
        })
    });

    group.finish();
}

criterion_group!(benches, bench_create_short_url);
criterion_main!(benches);
```

运行基准测试：

```bash
cargo criterion
# 结果会输出到 target/criterion/reports/index.html
```

### 7.2 用 Claude Code 分析火焰图

```bash
# 生成火焰图（需要安装 flamegraph）
cargo flamegraph --bench url_operations -- "url_operations"
```

然后向 Claude Code 提示：

```
请分析 target/flamegraph.svg 火焰图中的性能热点。
重点关注：
1. 是否有不必要的内存分配
2. 锁竞争点
3. I/O 瓶颈
给出具体的优化建议和代码改动。
```

### 7.3 常见性能优化模式

Claude Code 在 Rust 性能优化中特别擅长的领域：

| 优化方向 | Claude Code 能力 | 典型收益 |
|---------|-----------------|---------|
| 减少克隆 | 自动识别可用 `&str`/`Cow` 替代 `String` 的位置 | 10-30% 吞吐提升 |
| 零拷贝解析 | 使用 `&[u8]` + nom/winnow 替代 String 解析 | 2-5x 解析速度 |
| 连接池调优 | 根据负载分析建议 pool size | 减少 50% 等待时间 |
| 批量操作 | 将循环内的单条 INSERT 改为批量 | 10-100x 写入速度 |

---

## 第八步：FFI 与 unsafe 边界——Claude Code 的安全审查能力

当 Rust 项目需要调用 C 库时，unsafe 边界是最容易出问题的地方。Claude Code 可以帮助你：

### 8.1 让 Claude Code 生成安全封装

```
我需要调用 C 的 libcurl 进行 HTTP 请求。请：
1. 使用 rust-bindgen 生成 FFI 绑定
2. 创建安全封装层，确保所有 unsafe 块都有安全不变量注释
3. 实现 Drop trait 确保 curl_easy_handle 正确释放
```

Claude Code 生成的安全封装模式：

```rust
/// 安全封装 libcurl easy handle。
///
/// # 安全不变量
/// - inner 始终指向通过 curl_easy_init() 分配的有效 handle
/// - 同一 handle 不会被多个线程同时使用（Send 但非 Sync）
/// - response_data 仅在 perform 完成后访问
pub struct SafeCurl {
    inner: *mut curl_sys::CURL,
    response_data: Vec<u8>,
}

// SAFETY: curl easy handle 可以在线程间移动（Send），
// 但不能被多线程同时使用（非 Sync）。
unsafe impl Send for SafeCurl {}

impl SafeCurl {
    pub fn new() -> Result<Self, CurlError> {
        let handle = unsafe { curl_sys::curl_easy_init() };
        if handle.is_null() {
            return Err(CurlError::InitFailed);
        }
        Ok(Self {
            inner: handle,
            response_data: Vec::new(),
        })
    }

    pub fn perform_get(&mut self, url: &str) -> Result<&[u8], CurlError> {
        self.response_data.clear();

        let url_c = std::ffi::CString::new(url)
            .map_err(|_| CurlError::InvalidUrl)?;

        // SAFETY: inner 已验证非空，且 &mut self 保证独占访问
        unsafe {
            curl_sys::curl_easy_setopt(
                self.inner,
                curl_sys::CURLOPT_URL,
                url_c.as_ptr(),
            );

            // 设置写回调
            let data_ptr = &mut self.response_data as *mut Vec<u8>;
            curl_sys::curl_easy_setopt(
                self.inner,
                curl_sys::CURLOPT_WRITEDATA,
                data_ptr,
            );
            curl_sys::curl_easy_setopt(
                self.inner,
                curl_sys::CURLOPT_WRITEFUNCTION,
                write_callback,
            );

            let result = curl_sys::curl_easy_perform(self.inner);
            if result != curl_sys::CURLE_OK {
                return Err(CurlError::PerformFailed(result));
            }
        }

        Ok(&self.response_data)
    }
}

impl Drop for SafeCurl {
    fn drop(&mut self) {
        // SAFETY: inner 来自 curl_easy_init()，对应 curl_easy_cleanup()
        unsafe {
            curl_sys::curl_easy_cleanup(self.inner);
        }
    }
}

// 外部回调函数，将数据写入 Vec<u8>
unsafe extern "C" fn write_callback(
    ptr: *mut u8,
    size: usize,
    nmemb: usize,
    user_data: *mut std::ffi::c_void,
) -> usize {
    let data = &mut *(user_data as *mut Vec<u8>);
    let total = size * nmemb;
    let slice = std::slice::from_raw_parts(ptr, total);
    data.extend_from_slice(slice);
    total
}
```

### 8.2 unsafe 审查 Checklist

在 `CLAUDE.md` 中加入以下规则，让 Claude Code 自动遵循：

```markdown
## unsafe 审查规则
- 每个 unsafe 块必须有 `// SAFETY:` 注释说明不变量
- raw pointer 解引用前必须检查非空
- FFI 函数调用的参数生命周期必须超出调用
- 所有外部资源（malloc/handle/fd）必须有 Drop 实现
- 禁止在 safe 函数签名中暴露 raw pointer
```

---

## 实战案例完整工作流

下面是我们构建 URL 短链服务的完整工作流：

```mermaid
flowchart TD
    A[配置 CLAUDE.md] --> B[cargo init + 依赖]
    B --> C[定义错误模型]
    C --> D[存储层开发]
    D --> E{编译通过?}
    E -- 否 --> F[Claude Code 修复借用错误]
    F --> E
    E -- 是 --> G[缓存层集成]
    G --> H[Handler + 路由]
    H --> I[单元测试]
    I --> J{测试通过?}
    J -- 否 --> K[Claude Code 修复测试]
    K --> J
    J -- 是 --> L[集成测试]
    L --> M[Criterion 基准测试]
    M --> N[Flamegraph 性能分析]
    N --> O[生产就绪]

    style A fill:#f9f,stroke:#333
    style F fill:#ff9,stroke:#333
    style O fill:#9f9,stroke:#333
```

### 一键验证命令序列

```bash
# 1. 构建
cargo build --release

# 2. 测试
cargo nextest run

# 3. Lint
cargo clippy -- -D warnings

# 4. 格式
cargo fmt --check

# 5. 基准
cargo criterion

# 6. 启动服务
./target/release/url-shortener
```

在另一个终端验证 API：

```bash
# 创建短链
curl -X POST http://localhost:3000/shorten \
  -H "Content-Type: application/json" \
  -d '{"url": "https://doc.rust-lang.org/book/"}'

# 访问短链（假设返回 id = "Ab3x9Kf"）
curl -v http://localhost:3000/Ab3x9Kf

# 查看统计
curl http://localhost:3000/Ab3x9Kf/stats

# 删除
curl -X DELETE http://localhost:3000/Ab3x9Kf
```

---

## 常见问题与排错手册

### 问题一：Claude Code 生成的代码通不过借用检查器

**原因**：Claude Code 有时会生成在语义上正确但不满足 Rust 借用规则的代码。

**解法**：直接把编译器错误粘贴给 Claude Code，并加上：

```
请修复以下编译错误，要求：
1. 优先重构代码结构（如拆分借用、调整函数签名）
2. 其次考虑使用 Arc/Rc 共享所有权
3. 最后才考虑 clone，并注释说明为什么这里 clone 是可接受的
```

### 问题二：sqlx 编译时查询检查失败

**原因**：sqlx 的 `query!` 宏在编译时需要连接数据库验证 SQL。

**解法**：

```bash
# 方案 A：设置数据库 URL 环境变量
export DATABASE_URL="sqlite:url_shortener.db"
# 先运行一次 schema 初始化
cargo run  # 会创建数据库和表
# 然后正常编译
cargo build

# 方案 B：使用离线模式（CI 推荐）
cargo sqlx prepare  # 生成 .sqlx 目录
# CLAUDE.md 中注明：sqlx 使用离线模式，query! 宏检查基于 .sqlx/ 目录
```

### 问题三：Claude Code 生成了不必要的 clone

**解法**：在 CLAUDE.md 中明确声明：

```markdown
## 性能敏感区域
- handler 函数的热路径禁止 clone 大于 64 字节的结构体
- 优先使用引用传递 + lifetime 标注
- 如果必须 clone，在注释中说明原因
```

### 问题四：Tokio 运行时嵌套错误

```
错误信息：Cannot start a runtime from within a runtime
```

**解法**：这通常发生在测试中。确保：

```rust
// 错误：嵌套运行时
#[test]
fn test_bad() {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
        // 如果被测代码内部也创建了 runtime，就会 panic
    });
}

// 正确：使用 #[tokio::test]
#[tokio::test]
async fn test_good() {
    // 直接在 tokio 运行时中编写异步代码
}
```

---

## 进阶技巧：Rust Workspace 与 Claude Code

### 多 crate 项目配置

对于大型 Rust 项目，workspace 是标配。Claude Code 对 workspace 的支持非常自然：

```
url-shortener/
├── CLAUDE.md                    # 全局规则
├── Cargo.toml                   # workspace 定义
├── crates/
│   ├── core/
│   │   ├── CLAUDE.md            # 核心库规则
│   │   └── Cargo.toml
│   ├── api-server/
│   │   ├── CLAUDE.md            # API 服务规则
│   │   └── Cargo.toml
│   └── cli-client/
│       ├── CLAUDE.md            # CLI 客户端规则
│       └── Cargo.toml
└── tests/
    └── integration/
```

```toml
# 根 Cargo.toml
[workspace]
members = [
    "crates/core",
    "crates/api-server",
    "crates/cli-client",
]
resolver = "3"

[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
axum = "0.8"
```

每个子 crate 的 CLAUDE.md 可以指定独立的规则：

```markdown
# crates/core/CLAUDE.md
## 本 crate 规则
- 纯库 crate，禁止依赖 tokio/axum（保持同步兼容性）
- 公共 API 必须是 Send + Sync
- 错误类型使用 thiserror，不使用 anyhow

## 测试命令
- `cargo test -p url-shortener-core`
```

---

## 延伸阅读

1. [Claude Code Best Practices — 官方最佳实践](https://code.claude.com/docs/en/best-practices) — Anthropic 官方配置指南
2. [Coding Rust with Claude Code and Codex — tigran.tech](https://tigran.tech/coding-rust-with-claude-code-and-codex/) — Rust 编译器如何成为 AI 编码的最佳搭档
3. [Rust Development Patterns — Developer Toolkit](https://developertoolkit.ai/en/cookbook/backend-recipes/rust-patterns/) — AI 辅助 Rust 开发模式大全
4. [Rust Claude Code Starter Template — GitHub](https://github.com/iepathos/rust-claude-code) — 开箱即用的 Rust + Claude Code 项目模板
5. [Build with Naz: Claude Code + Rust Profiling — YouTube](https://www.youtube.com/watch?v=7iLMdNc-zOs) — 实战性能分析视频教程

---

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| 适合训练营类型 | Rust 进阶训练营、AI 工程化训练营、后端性能优化训练营 |
| 预计教学时长 | 4-6 小时（含动手实操） |
| 学员前置要求 | Rust 基础语法（所有权、生命周期）、HTTP API 基础概念 |
| 硬件要求 | 4GB+ RAM（SQLite + Cargo 编译需要） |
| 难度系数 | ★★★☆☆（中等偏上，借用检查器部分是主要难点） |
| 可扩展方向 | 添加 Redis 缓存层、实现 rate limiting、集成 Prometheus 指标、WASM 边缘部署 |

---

## 参考来源

1. [How to Use Claude Code for Rust Development: The Complete Workflow — getbeam.dev](https://getbeam.dev/blog/claude-code-rust-development.html) — Claude Code Rust 全流程开发工作流指南
2. [Coding Rust With Claude Code and Codex — HackerNoon](https://hackernoon.com/coding-rust-with-claude-code-and-codex) — AI 编码工具在 Rust 项目中的对比实践
3. [Coding Rust with Claude Code and Codex — tigran.tech](https://tigran.tech/coding-rust-with-claude-code-and-codex/) — Rust 编译器与 AI 工具的协同增益分析
4. [Rust Claude Code Starter Template — GitHub](https://github.com/iepathos/rust-claude-code) — 社区维护的 Rust + Claude Code 项目模板
5. [Best AI Coding Tools for Rust in 2026 — devtoolsreview.com](https://devtoolsreview.com/best-for/best-ai-coding-tools-rust/) — 五大 AI 工具在 Rust 开发中的对比评测

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
