/rust-dev
Rust 开发规范(聚焦 Axum + Tokio 后端栈),覆盖工具链、错误处理、异步陷阱、模块组织、依赖管理与测试规范
$ npx -y skills add doccker/cc-use-exp --skill rust-dev --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/rust-dev
Context preview
The summary Claude sees to decide when to auto-load this skill.
Rust 开发规范(聚焦 Axum + Tokio 后端栈),覆盖工具链、错误处理、异步陷阱、模块组织、依赖管理与测试规范
SKILL.md
rust-dev.SKILL.mdname: rust-dev
description: Rust 开发规范(聚焦 Axum + Tokio 后端栈),覆盖工具链、错误处理、异步陷阱、模块组织、依赖管理与测试规范
version: v1.0
paths:
- "**/*.rs"
- "**/Cargo.toml"
- "**/Cargo.lock"
Rust 开发规范
> 参考来源:Rust API Guidelines、Rust Async Book、tokio.rs、Axum 官方示例、Effective Rust
**适用范围**:后端 Web 服务(Axum + Tokio 路线)。CLI/wasm/嵌入式不在本规范覆盖范围内。
---
工具链
# 格式化
cargo fmt --all
# 静态检查,warning 当 error
cargo clippy --all-targets -- -D warnings
# 测试
cargo test --all-features
# CVE 扫描(独立安装)
cargo audit
# 许可证/重复依赖检查(独立安装)
cargo deny check
# 检测未使用依赖
cargo machete
**edition**:新项目默认 `edition = "2024"`,老项目保持 `2021` 不强迁。
**版本基线**(写死,避免重复决策):
- Tokio `1.x`,Axum `0.8`,tower-http `0.6`
- serde `1`、tracing `0.1`、tracing-subscriber `0.3`
- DB:sqlx `0.8`(runtime-tokio-rustls)
---
命名约定
| 类型 | 规则 | 示例 | |------|------|------| | 模块/文件 | snake_case | `user_service.rs` | | 函数/变量 | snake_case | `fetch_user`, `db_pool` | | 类型/Trait/Enum | UpperCamelCase | `UserRepo`, `AppError` | | 常量/静态 | SCREAMING_SNAKE_CASE | `MAX_RETRY` | | 泛型 | 单大写或 UpperCamelCase | `T`, `Req`, `Resp` | | 生命周期 | 短小写 | `'a`, `'ctx` |
**禁止**:`common`、`util`、`helper`、`base` 等无意义模块名。模块按职责命名(`api`、`db`、`service`)。
---
模块组织
参照 `apple-store-price-rs` / `org-site-backend` 的实证拆分,按职责切文件,**不堆 `lib.rs`**:
src/
├── main.rs # 仅 bootstrap:日志初始化、读配置、建路由、启服务
├── config.rs # 配置加载(env / toml)
├── error.rs # AppError + IntoResponse impl
├── db.rs # 数据库连接池/迁移
├── api.rs # 路由聚合 + handler
├── service/ # 业务逻辑(无副作用层)
│ └── user.rs
└── models.rs # 领域类型 + serde 派生
**可见性**:`pub(crate)` 优先于 `pub`。只在确实要对外暴露时才用 `pub`。
---
错误处理(核心)
**分场景选边**:
| 场景 | 选择 | 理由 | |------|------|------| | 库 crate | `thiserror` | 调用方需要按变体匹配 | | 二进制应用顶层 | `anyhow` | 只关心"成功/失败 + 上下文" | | 跨边界(handler / 公共 API) | 自定义 `AppError` + `IntoResponse` | 控制对外错误形态 |
**禁止**生产代码出现:
- `.unwrap()` / `.expect()`(仅测试和 `const` 上下文可用)
- `panic!()`(仅用于"逻辑上不可达"且带 SAFETY 注释)
clippy 配置(在 `Cargo.toml` 或 `clippy.toml`):
[lints.clippy]
unwrap_used = "deny"
expect_used = "deny"
panic = "deny"
**错误传播用 `?`**,跨类型用 `From` 实现自动转换:
#[derive(thiserror::Error, Debug)]
pub enum AppError {
#[error("db error: {0}")]
Db(#[from] sqlx::Error),
#[error("not found")]
NotFound,
#[error("bad request: {0}")]
BadRequest(String),
}详见 `references/async-axum.md` 的 `IntoResponse` 实现。
---
异步编程红线
**1. 阻塞调用必须丢到 `spawn_blocking`**
// ❌ 在 async fn 里直接调用阻塞 API
let data = std::fs::read("big.bin")?;
// ✅
let data = tokio::task::spawn_blocking(|| std::fs::read("big.bin")).await??;判定标准:CPU 密集 > 10ms、`std::fs`、`std::thread::sleep`、同步 DB driver、`reqwest::blocking` —— 都必须 `spawn_blocking`。
**2. 不要跨 `.await` 持有 `std::sync::Mutex`**
// ❌ 编译能过,运行时死锁/性能崩溃
let g = std_mutex.lock().unwrap();
do_async().await;
// ✅ 用 tokio::sync::Mutex
let g = tokio_mutex.lock().await;
do_async().await;
**3. `Rc` / `RefCell` 不能跨 `.await`**:违反 `Send` 边界,编译报错,改用 `Arc` / `Mutex`。
**4. `tokio::select!` 分支必须取消安全**
- 取消安全 ✅:`tokio::time::sleep`、`channel.recv`、`tokio::io::AsyncRead` 标记方法
- 取消不安全 ❌:自己实现的 `async fn`(取消时可能丢数据)
不确定时,把状态保存到 `select!` 外部变量,分支只读写本地变量。
**5. 优雅停机**:用 `tokio::signal::ctrl_c()` + `axum::serve(...).with_graceful_shutdown()`。
更多陷阱见 `references/async-axum.md`。
---
所有权与借用最小集
**函数参数**:
| 想要 | 用 | 不要用 | |------|------|------| | 只读字符串 | `&str` | `&String` | | 只读切片 | `&[T]` | `&Vec<T>` | | 路径 | `&Path` | `&PathBuf` / `&str` | | 字符串入参可能多种来源 | `impl AsRef<str>` | 多个重载 |
**共享决策树**:
需要跨线程共享?
├── 否 → 用 Box<T> 或值传递
└── 是 → Arc<T>
├── 不可变共享 → Arc<T>
├── 读多写少 → Arc<RwLock<T>>
└── 读写均衡 → Arc<Mutex<T>>**`Clone` 不是坏事**,但循环内 `Clone` 大对象要警觉,考虑 `Arc` 或 `Cow<'_, T>`。
---
Axum 实战要点
type AppState = Arc<InnerState>;
let app = Router::new()
.nest("/api/users", user::routes()) // 按模块拆 + nest
.layer(TraceLayer::new_for_http()) // 日志中间件
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.with_state(state);- **State 一律 `Arc` 包装**,不要克隆裸结构体
- **Handler 错误类型实现 `IntoResponse`**,统一对外形态(status + json body)
- **路由按模块拆 `fn routes() -> Router<AppState>`**,避免 `main.rs` 膨胀
- **中间件走 `tower-http`**:CORS / 超时 / 限流 / 日志 / 静态文件
详见 `references/async-axum.md`。
---
依赖管理
**版本指定**:`Cargo.toml` 默认 caret(`"1"` 等价 `^1`),不要锁死 `=1.2.3` 除非有兼容性问题。
**features 显式声明**,禁止盲依赖默认值:
# ✅ 显式开启需要的 feature
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }**TLS 选 `rustls`**,避免 `openssl`(跨平台编译麻烦)。
**升级前先 dry-run**:
cargo update --dry-run
# 独立安装
cargo outdated
`Cargo.lock` 二进制项目必须提交,库项目不提交。
---
测试规范
// 异步测试用 #[tokio::test]
#[tokio::test]
async fn fetches_user() {
let svc = test_service().await;
let user = svc.find(1).await.unwrap(); // 测试代码可以 .unwrap()
assert_eq!(user.name, "alice");
}
// 表驱动:可选用 rstest crate
#[rstest::rstest]
#[case(1, 2, 3)]
#[case(-1, 1, 0)]
fn adds(#[case] a: i32, #[case] b: i32, #[case] expected: i32) {
assert_eq!(a + b, expected);
}- **单元测试**:`#[cfg(test)] mod tests { ... }` 紧贴被测代码
- **集成测试**:`tests/` 顶层目录,每个文件独立 crate
- **测试中允许 `.unwrap()`**(失败即测试失败,正是预期)
---
unsafe 红线
**业务代码原则禁用 `unsafe`**。
如必须使用(FFI / 极端性能场景): 1. 块前必须有 `// SAFETY: ...` 注释,说明保持的不变量 2. 缩小 `unsafe` 块到最小范围 3. 优先封装成 safe API 对外暴露 4. 走 code review,单独标记审查
---
性能要点
| 陷阱 | 解决 | |------|------| | 循环内 `format!` / 字符串拼接 | 用 `String::with_capacity` + `write!` | | Vec 频繁扩容 | `Vec::with_capacity(n)` | | 循环内 `.clone()` 大对象 | 借用或 `Arc` | | `Box<dyn Trait>` 滥用 | 优先泛型 `<T: Trait>`(静态分发) | | `String` 当 key | 优先 `&str` 或 `Cow<'static, str>` | | 阻塞 IO 在 async 里 | `spawn_blocking`(见异步章节) |
性能分析:
cargo build --release
# 火焰图(独立安装)
cargo flamegraph
# criterion 基准测试
cargo bench
---
详细参考
| 文件 | 内容 | |------|------| | `references/rust-style.md` | 命名细则、`From`/`TryFrom` 规范、newtype
Read more
name: rust-dev description: Rust 开发规范(聚焦 Axum + Tokio 后端栈),覆盖工具链、错误处理、异步陷阱、模块组织、依赖管理与测试规范 version: v1.0 paths: - "**/*.rs" - "**/Cargo.toml" - "**/Cargo.lock"
Rust 开发规范
> 参考来源:Rust API Guidelines、Rust Async Book、tokio.rs、Axum 官方示例、Effective Rust
**适用范围**:后端 Web 服务(Axum + Tokio 路线)。CLI/wasm/嵌入式不在本规范覆盖范围内。
---
工具链
# 格式化 cargo fmt --all # 静态检查,warning 当 error cargo clippy --all-targets -- -D warnings # 测试 cargo test --all-features # CVE 扫描(独立安装) cargo audit # 许可证/重复依赖检查(独立安装) cargo deny check # 检测未使用依赖 cargo machete
**edition**:新项目默认 `edition = "2024"`,老项目保持 `2021` 不强迁。
**版本基线**(写死,避免重复决策):
- Tokio `1.x`,Axum `0.8`,tower-http `0.6`
- serde `1`、tracing `0.1`、tracing-subscriber `0.3`
- DB:sqlx `0.8`(runtime-tokio-rustls)
---
命名约定
| 类型 | 规则 | 示例 | |------|------|------| | 模块/文件 | snake_case | `user_service.rs` | | 函数/变量 | snake_case | `fetch_user`, `db_pool` | | 类型/Trait/Enum | UpperCamelCase | `UserRepo`, `AppError` | | 常量/静态 | SCREAMING_SNAKE_CASE | `MAX_RETRY` | | 泛型 | 单大写或 UpperCamelCase | `T`, `Req`, `Resp` | | 生命周期 | 短小写 | `'a`, `'ctx` |
**禁止**:`common`、`util`、`helper`、`base` 等无意义模块名。模块按职责命名(`api`、`db`、`service`)。
---
模块组织
参照 `apple-store-price-rs` / `org-site-backend` 的实证拆分,按职责切文件,**不堆 `lib.rs`**:
src/ ├── main.rs # 仅 bootstrap:日志初始化、读配置、建路由、启服务 ├── config.rs # 配置加载(env / toml) ├── error.rs # AppError + IntoResponse impl ├── db.rs # 数据库连接池/迁移 ├── api.rs # 路由聚合 + handler ├── service/ # 业务逻辑(无副作用层) │ └── user.rs └── models.rs # 领域类型 + serde 派生
**可见性**:`pub(crate)` 优先于 `pub`。只在确实要对外暴露时才用 `pub`。
---
错误处理(核心)
**分场景选边**:
| 场景 | 选择 | 理由 | |------|------|------| | 库 crate | `thiserror` | 调用方需要按变体匹配 | | 二进制应用顶层 | `anyhow` | 只关心"成功/失败 + 上下文" | | 跨边界(handler / 公共 API) | 自定义 `AppError` + `IntoResponse` | 控制对外错误形态 |
**禁止**生产代码出现:
- `.unwrap()` / `.expect()`(仅测试和 `const` 上下文可用)
- `panic!()`(仅用于"逻辑上不可达"且带 SAFETY 注释)
clippy 配置(在 `Cargo.toml` 或 `clippy.toml`):
[lints.clippy] unwrap_used = "deny" expect_used = "deny" panic = "deny"
**错误传播用 `?`**,跨类型用 `From` 实现自动转换:
#[derive(thiserror::Error, Debug)]
pub enum AppError {
#[error("db error: {0}")]
Db(#[from] sqlx::Error),
#[error("not found")]
NotFound,
#[error("bad request: {0}")]
BadRequest(String),
}详见 `references/async-axum.md` 的 `IntoResponse` 实现。
---
异步编程红线
**1. 阻塞调用必须丢到 `spawn_blocking`**
// ❌ 在 async fn 里直接调用阻塞 API
let data = std::fs::read("big.bin")?;
// ✅
let data = tokio::task::spawn_blocking(|| std::fs::read("big.bin")).await??;判定标准:CPU 密集 > 10ms、`std::fs`、`std::thread::sleep`、同步 DB driver、`reqwest::blocking` —— 都必须 `spawn_blocking`。
**2. 不要跨 `.await` 持有 `std::sync::Mutex`**
// ❌ 编译能过,运行时死锁/性能崩溃 let g = std_mutex.lock().unwrap(); do_async().await; // ✅ 用 tokio::sync::Mutex let g = tokio_mutex.lock().await; do_async().await;
**3. `Rc` / `RefCell` 不能跨 `.await`**:违反 `Send` 边界,编译报错,改用 `Arc` / `Mutex`。
**4. `tokio::select!` 分支必须取消安全**
- 取消安全 ✅:`tokio::time::sleep`、`channel.recv`、`tokio::io::AsyncRead` 标记方法
- 取消不安全 ❌:自己实现的 `async fn`(取消时可能丢数据)
不确定时,把状态保存到 `select!` 外部变量,分支只读写本地变量。
**5. 优雅停机**:用 `tokio::signal::ctrl_c()` + `axum::serve(...).with_graceful_shutdown()`。
更多陷阱见 `references/async-axum.md`。
---
所有权与借用最小集
**函数参数**:
| 想要 | 用 | 不要用 | |------|------|------| | 只读字符串 | `&str` | `&String` | | 只读切片 | `&[T]` | `&Vec<T>` | | 路径 | `&Path` | `&PathBuf` / `&str` | | 字符串入参可能多种来源 | `impl AsRef<str>` | 多个重载 |
**共享决策树**:
需要跨线程共享?
├── 否 → 用 Box<T> 或值传递
└── 是 → Arc<T>
├── 不可变共享 → Arc<T>
├── 读多写少 → Arc<RwLock<T>>
└── 读写均衡 → Arc<Mutex<T>>**`Clone` 不是坏事**,但循环内 `Clone` 大对象要警觉,考虑 `Arc` 或 `Cow<'_, T>`。
---
Axum 实战要点
type AppState = Arc<InnerState>;
let app = Router::new()
.nest("/api/users", user::routes()) // 按模块拆 + nest
.layer(TraceLayer::new_for_http()) // 日志中间件
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.with_state(state);- **State 一律 `Arc` 包装**,不要克隆裸结构体
- **Handler 错误类型实现 `IntoResponse`**,统一对外形态(status + json body)
- **路由按模块拆 `fn routes() -> Router<AppState>`**,避免 `main.rs` 膨胀
- **中间件走 `tower-http`**:CORS / 超时 / 限流 / 日志 / 静态文件
详见 `references/async-axum.md`。
---
依赖管理
**版本指定**:`Cargo.toml` 默认 caret(`"1"` 等价 `^1`),不要锁死 `=1.2.3` 除非有兼容性问题。
**features 显式声明**,禁止盲依赖默认值:
# ✅ 显式开启需要的 feature
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }**TLS 选 `rustls`**,避免 `openssl`(跨平台编译麻烦)。
**升级前先 dry-run**:
cargo update --dry-run # 独立安装 cargo outdated
`Cargo.lock` 二进制项目必须提交,库项目不提交。
---
测试规范
// 异步测试用 #[tokio::test]
#[tokio::test]
async fn fetches_user() {
let svc = test_service().await;
let user = svc.find(1).await.unwrap(); // 测试代码可以 .unwrap()
assert_eq!(user.name, "alice");
}
// 表驱动:可选用 rstest crate
#[rstest::rstest]
#[case(1, 2, 3)]
#[case(-1, 1, 0)]
fn adds(#[case] a: i32, #[case] b: i32, #[case] expected: i32) {
assert_eq!(a + b, expected);
}- **单元测试**:`#[cfg(test)] mod tests { ... }` 紧贴被测代码
- **集成测试**:`tests/` 顶层目录,每个文件独立 crate
- **测试中允许 `.unwrap()`**(失败即测试失败,正是预期)
---
unsafe 红线
**业务代码原则禁用 `unsafe`**。
如必须使用(FFI / 极端性能场景): 1. 块前必须有 `// SAFETY: ...` 注释,说明保持的不变量 2. 缩小 `unsafe` 块到最小范围 3. 优先封装成 safe API 对外暴露 4. 走 code review,单独标记审查
---
性能要点
| 陷阱 | 解决 | |------|------| | 循环内 `format!` / 字符串拼接 | 用 `String::with_capacity` + `write!` | | Vec 频繁扩容 | `Vec::with_capacity(n)` | | 循环内 `.clone()` 大对象 | 借用或 `Arc` | | `Box<dyn Trait>` 滥用 | 优先泛型 `<T: Trait>`(静态分发) | | `String` 当 key | 优先 `&str` 或 `Cow<'static, str>` | | 阻塞 IO 在 async 里 | `spawn_blocking`(见异步章节) |
性能分析:
cargo build --release # 火焰图(独立安装) cargo flamegraph # criterion 基准测试 cargo bench
---
详细参考
| 文件 | 内容 | |------|------| | `references/rust-style.md` | 命名细则、`From`/`TryFrom` 规范、newtype
保留你熟悉的 CLI/IDE,让 Claude Code、Gemini CLI、Codex、Cursor、GitHub Copilot 开箱即用 按费力度从低到高,用最少操作获得最大帮助 不是提示词集合,而是一套可维护的 AI 协作配置系统。
Repo: doccker/cc-use-exp
Other skills on cc-use-exp.
- /api-design-safety
当设计或修改 REST API 响应结构、处理 API 返回值,或生成 Excel/CSV/PDF/对账文件等下游产物时触发。防止 API 设计缺陷导致的字段错位、类型歧义,以及生成产物时关键字段缺失但静默成功的问题。
Open skill - /api-proxy-safety
网关/代理/WAF/CDN 中间件的安全关键词匹配实现规范,防止纯子串匹配误判正常响应内容中的技术术语(如 Cloudflare、502、error)
Open skill - /async-task-pattern
当 API/任务可能执行超过 10 秒(批量数据处理、远程 API 批量调用、全表扫描、跨租户聚合)时触发。防止同步接口被网关 30s 超时切断、用户重复点击触发并发、状态缓存内存泄漏等问题。提供异步任务状态机标准模板。
Open skill - /bash-style
当用户操作 .sh、Dockerfile、Makefile、.yml、.yaml 文件,或在 Markdown 中编写 bash 代码块时触发。提供 Bash 编写规范。
Open skill - /code-quality-principles
当编写新模块、设计接口、重构代码或代码审查时触发。提供经典模块化六原则检查清单(大小适中/调用深度/扇入扇出/边界清晰/作用域内聚/可预测性),适用于 PR/Review/新模块设计场景。
Open skill - /external-system-debugging
涉及浏览器、编辑器、CDN/WAF、IM 平台、操作系统剪贴板、第三方 SaaS 等"外部黑盒系统"的代码编写或 bug 调试时触发。强制先抓真实环境数据再推理,避免连续 2 轮"凭代码推理"的修复 no-op。关键词:粘贴/复制异常、跨平台显示不一致、第三方 API 怪结果、CDN/WAF 拦截、本地复现失败、HTML→MD 转换丢属性。
Open skill

