mcpField
R
rust-expert

rust-expert

github.com/oimiragieo开发4562 次安装18 周前更新
代码仓库

关于 rust-expert

面向 Rust 开发的 Skill,强调安全、性能与可维护性的惯用实践,涵盖所有权、错误处理、异步代码、测试、不安全 Rust 和构建规范。

主要功能

  1. 01应用所有权与借用模式
  2. 02使用带上下文的错误处理
  3. 03指导 Tokio 异步与并发
  4. 04提供测试和性能分析建议
  5. 05涵盖不安全 Rust、FFI 和 Cargo

适用场景

  1. 01检查 Rust 代码的惯用写法
  2. 02设计错误处理与异步任务
  3. 03规划 Rust crate 的测试
  4. 04评估不安全代码和 FFI 边界

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/oimiragieo/agent-studio --skill rust-expert

安装后在 Claude Code 中输入/rust-expert确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

commands/
rust-expert.md
hooks/
post-execute.cjs
pre-execute.cjs
references/
research-requirements.md
rules/
rust-expert.md
schemas/
input.schema.json
output.schema.json
scripts/
main.cjs
templates/
implementation-template.md

详细介绍

Rust 专家

应用符合 Rust 惯用方式的模式,确保安全性、性能和可维护性。

核心原则

  • 通过类型系统建模正确性;让无效状态无法表示。
  • 优先显式而非隐式;在每个签名中体现所有权意图。
  • 编写精简、零成本的抽象——不做无意义的分配或间接访问。
  • 测试行为,而非实现;优先在 crate 边界编写集成测试。

1. 所有权、借用与生命周期

所有权模式
// Prefer moves when value is consumed; prefer borrows when value is shared.
fn process(data: Vec<u8>) -> Vec<u8> { /* consumes */ }
fn inspect(data: &[u8]) -> usize { data.len() /* borrows */ }

// Clone only when necessary and explicitly documented.
// Anti-pattern: clone() to silence borrow checker errors — fix the design instead.
借用规则(在设计阶段落实)
  • 同一时刻只能有一个可变引用,或任意数量的不可变引用。
  • 引用的生命周期不得超过其指向的值。
  • 需要借用或拥有语义、又不想强制分配时,使用 Cow<'a, T>。
use std::borrow::Cow;

fn normalize(s: &str) -> Cow<str> {
    if s.contains(' ') {
        Cow::Owned(s.replace(' ', "_"))
    } else {
        Cow::Borrowed(s)
    }
}
生命周期模式
// Explicit lifetime annotation: only when compiler cannot infer.
struct Parser<'input> {
    source: &'input str,
    pos: usize,
}

impl<'input> Parser<'input> {
    fn next_token(&mut self) -> &'input str {
        // Returns a slice of the original input — lifetime ties result to source.
        &self.source[self.pos..]
    }
}

// RPIT (Return Position Impl Trait) avoids lifetime noise in many cases.
fn words(s: &str) -> impl Iterator<Item = &str> {
    s.split_whitespace()
}
应避免的反模式
  • 在热循环中调用 clone() 来规避借用检查器。
  • 使用 unsafe 绕过生命周期检查——应重新设计。
  • 在借用已足够时使用会强制堆分配的 'static 约束。
  • 在结构体中存储 &mut T——优先使用自有数据或 RefCell<T>。

2. 错误处理

决策矩阵
场景工具
库 crate 错误thiserror — 类型化、可组合
应用程序 / 二进制文件错误anyhow — 便捷的 ? 链式传播
特定领域上下文通过 thiserror 定义自定义枚举
不会失败的转换From / Into — 零开销
thiserror(库 crate)
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ConfigError {
    #[error("missing required field: {field}")]
    MissingField { field: &'static str },
    #[error("invalid value for {field}: {source}")]
    ParseError { field: &'static str, #[source] source: std::num::ParseIntError },
    #[error(transparent)]
    Io(#[from] std::io::Error),
}
anyhow(应用程序 / 二进制文件)
use anyhow::{Context, Result};

fn load_config(path: &str) -> Result<Config> {
    let raw = std::fs::read_to_string(path)
        .with_context(|| format!("reading config from {path}"))?;
    toml::from_str(&raw).context("parsing config TOML")
}
错误传播规则
  • 库代码中绝不使用 .unwrap();仅在测试和 main() 中使用 .expect(),且不变量必须显而易见。
  • 在每个错误边界添加 .context() / .with_context(),以保留调用链。
  • 在 crate 边界映射错误——不要在公共 API 中泄露内部错误类型。

3. Trait 系统

泛型与 Trait 对象
// Prefer generics (monomorphized, zero-cost) when types are known at compile time.
fn serialize<S: Serialize>(value: &S) -> Vec<u8> { /* ... */ }

// Use dyn Trait only when you need heterogeneous collections or runtime dispatch.
fn handlers() -> Vec<Box<dyn EventHandler>> { /* ... */ }
Where 子句
// Prefer where clauses for readability when bounds are complex.
fn merge<K, V>(a: HashMap<K, V>, b: HashMap<K, V>) -> HashMap<K, V>
where
    K: Eq + Hash,
    V: Clone,
{ /* ... */ }
Blanket 实现与孤儿规则
  • 为自己的类型实现标准 trait(Display、From、Iterator)。
  • 遵守孤儿规则:只能为本地类型实现外部 trait。
  • 使用 newtype 模式绕过孤儿限制。
struct Wrapper(Vec<u8>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", self.0)
    }
}
Rust 1.75+ 中 Trait 的 Async(RPITIT)
// Stable as of Rust 1.75 — no more async-trait macro needed.
trait DataSource {
    async fn fetch(&self, id: u64) -> anyhow::Result<Record>;
}

// Return-Position Impl Trait in Traits (RPITIT) — also stable in 1.75.
trait Transformer {
    fn transform(&self, input: &str) -> impl Iterator<Item = String>;
}

4. 使用 Tokio 进行 Async / Await

Tokio 运行时设置
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Default: multi-thread runtime (all CPU cores).
    Ok(())
}

// Single-thread runtime for I/O-only workloads.
#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> { Ok(()) }
任务生成
use tokio::task;

// Spawn a non-blocking async task.
let handle = task::spawn(async move {
    fetch_data(url).await
});
let result = handle.await?; // propagate JoinError

// CPU-bound work must go to the blocking thread pool — never block the async runtime.
let result = task::spawn_blocking(|| {
    expensive_cpu_computation()
}).await?;
通道
use tokio::sync::{mpsc, oneshot, broadcast, watch};

// mpsc: producer → consumer pipeline.
let (tx, mut rx) = mpsc::channel::<Bytes>(1024);

// oneshot: request / response pattern.
let (resp_tx, resp_rx) = oneshot::channel::<Result<Record>>();

// broadcast: fan-out to multiple subscribers.
let (tx, _rx) = broadcast::channel::<Event>(16);

// watch: latest-value semantics (config reload, health state).
let (tx, rx) = watch::channel(Config::default());
select! 与取消
use tokio::select;
use tokio_util::sync::CancellationToken;

async fn worker(token: CancellationToken) {
    select! {
        _ = token.cancelled() => {
            // Structured cancellation — always handle shutdown path.
        }
        result = do_work() => {
            // Normal completion.
        }
    }
}
结构化并发
use tokio::task::JoinSet;

async fn fetch_all(urls: Vec<String>) -> Vec<anyhow::Result<Bytes>> {
    let mut set = JoinSet::new();
    for url in urls {
        set.spawn(fetch(url));
    }
    let mut results = Vec::new();
    while let Some(res) = set.join_next().await {
        results.push(res.expect("task panicked"));
    }
    results
}
Async 反模式
  • 在 async 上下文中使用 std::thread::sleep——改用 tokio::time::sleep。
  • 在 .await 期间持有 MutexGuard——改用 tokio::sync::Mutex。
  • 在 async 线程上执行阻塞 I/O(读取文件、DNS)——使用 spawn_blocking 或 tokio::fs。
  • 使用无界通道——始终设置容量上限。

5. 常用 Crate

serde — 序列化
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct User {
    pub id: u64,
    pub display_name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,
}
axum — HTTP 服务端
use axum::{extract::{Path, State}, routing::get, Json, Router};

async fn get_user(
    State(db): State<DbPool>,
    Path(id): Path<u64>,
) -> Result<Json<User>, AppError> {
    let user = db.find_user(id).await?;
    Ok(Json(user))
}

let app = Router::new()
    .route("/users/:id", get(get_user))
    .with_state(db_pool);
sqlx — Async 数据库
use sqlx::PgPool;

async fn insert_user(pool: &PgPool, name: &str) -> sqlx::Result<i64> {
    let row = sqlx::query!("INSERT INTO users (name) VALUES ($1) RETURNING id", name)
        .fetch_one(pool)
        .await?;
    Ok(row.id)
}
reqwest — HTTP 客户端
use reqwest::Client;

async fn fetch_json<T: for<'de> serde::Deserialize<'de>>(
    client: &Client,
    url: &str,
) -> anyhow::Result<T> {
    Ok(client.get(url).send().await?.error_for_status()?.json().await?)
}
rayon — 数据并行
use rayon::prelude::*;

fn parallel_sum(data: &[f64]) -> f64 {
    data.par_iter().sum()
}
clap — CLI
use clap::Parser;

#[derive(Parser, Debug)]
#[command(version, about)]
struct Args {
    #[arg(short, long, default_value = "8080")]
    port: u16,

    #[arg(short, long, env = "CONFIG_PATH")]
    config: std::path::PathBuf,
}

6. 性能优化

零成本抽象
  • 优先使用迭代器,而非手动索引循环——它们会编译为相同的机器码。
  • 对跨 crate 边界的热路径小函数使用 #[inline]。
  • 优先使用栈分配;仅在必要时移至堆(Box、Vec、Arc)。
SIMD
// Use std::simd (nightly) or portable-simd crate for explicit vectorization.
// Profile first — LLVM auto-vectorizes most iterator chains.
use std::simd::{f32x8, SimdFloat};

fn dot_product_simd(a: &[f32], b: &[f32]) -> f32 {
    a.chunks_exact(8)
        .zip(b.chunks_exact(8))
        .map(|(a_chunk, b_chunk)| {
            let va = f32x8::from_slice(a_chunk);
            let vb = f32x8::from_slice(b_chunk);
            (va * vb).reduce_sum()
        })
        .sum()
}
性能分析
# flamegraph (install: cargo install flamegraph)
cargo flamegraph --bin my-app

# perf stat for CPU counters (Linux)
perf stat cargo run --release

# heaptrack for heap allocation analysis (Linux)
heaptrack cargo run --release

# criterion for micro-benchmarks
cargo bench
分配意识
// Preallocate when size is known.
let mut v = Vec::with_capacity(expected_len);

// String building: use write! into a pre-allocated String.
use std::fmt::Write;
let mut s = String::with_capacity(256);
write!(s, "id={}", id)?;

// Avoid format!() in hot paths — prefer direct write!() or push_str().

7. 测试

单元测试
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_valid_config() {
        let cfg = Config::from_str("[server]\nport = 9000").unwrap();
        assert_eq!(cfg.port, 9000);
    }

    #[test]
    fn parse_invalid_port_returns_error() {
        let result = Config::from_str("[server]\nport = -1");
        assert!(result.is_err());
    }
}
集成测试
// tests/integration/server.rs — tests the full public API.
#[tokio::test]
async fn health_endpoint_returns_200() {
    let addr = spawn_test_server().await;
    let resp = reqwest::get(format!("http://{addr}/health")).await.unwrap();
    assert_eq!(resp.status(), 200);
}
基于属性的测试(proptest)
use proptest::prelude::*;

proptest! {
    #[test]
    fn encode_decode_roundtrip(data: Vec<u8>) {
        let encoded = encode(&data);
        prop_assert_eq!(decode(&encoded).unwrap(), data);
    }
}
Async 测试
#[tokio::test]
async fn fetch_returns_data() {
    let mock = MockServer::start().await;
    Mock::given(method("GET")).respond_with(ResponseTemplate::new(200)).mount(&mock).await;
    let result = fetch(&mock.uri()).await;
    assert!(result.is_ok());
}
测试约定
  • 每个测试函数只测试一种行为。
  • 使用描述性名称:<function>_<scenario>_<expected>。
  • 只模拟外部 I/O 边界(HTTP、文件系统、数据库)。
  • 开发期间运行 cargo test -- --nocapture,以输出诊断信息。
  • 在 CI 中运行 cargo nextest run,以更快地并行执行测试。

8. 不安全 Rust

适用场景
  • FFI 边界(调用 C 函数、向 C 导出)。
  • 安全抽象无法满足需求时,使用底层内存映射或 SIMD 内建函数。
  • 安全替代方案存在经性能分析证实的显著开销时,用于性能关键代码。
指南
/// # Safety
///
/// - `ptr` must be non-null and properly aligned for `T`.
/// - The memory at `ptr` must be valid for `len` elements of type `T`.
/// - The caller must ensure no other mutable references to the memory exist.
pub unsafe fn from_raw_parts<T>(ptr: *const T, len: usize) -> &'static [T] {
    std::slice::from_raw_parts(ptr, len)
}
  • 每个 unsafe 块都必须有一条 // SAFETY: 注释,说明其为何是健全的。
  • 尽可能缩小 unsafe 的作用范围——立即将其封装在安全抽象中。
  • 如果整个函数都是不安全的,优先使用 unsafe fn,而不是在安全函数中使用 unsafe 块。
  • 使用 Miri(cargo +nightly miri test)检测不安全代码中的未定义行为。

9. FFI 与互操作

导出到 C
#[no_mangle]
pub extern "C" fn my_add(a: i32, b: i32) -> i32 {
    a + b
}
在 Rust 中调用 C
extern "C" {
    fn strlen(s: *const std::os::raw::c_char) -> usize;
}

fn rust_strlen(s: &str) -> usize {
    let cstr = std::ffi::CString::new(s).expect("no null bytes");
    // SAFETY: cstr is a valid, null-terminated C string.
    unsafe { strlen(cstr.as_ptr()) }
}
cbindgen / bindgen
  • 使用 cbindgen 根据 Rust 公共 API 生成 C 头文件。
  • 使用 bindgen 根据 C 头文件生成 Rust 绑定。
  • 始终通过 CI 运行,以检测 API 漂移。

10. 构建系统

Cargo 工作区
# Cargo.toml (workspace root)
[workspace]
members = ["crates/core", "crates/server", "crates/cli"]
resolver = "2"

[workspace.dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }

# crates/core/Cargo.toml
[dependencies]
tokio.workspace = true
serde.workspace = true
功能标志
[features]
default = ["std"]
std = []
async = ["dep:tokio"]
metrics = ["dep:prometheus"]

[dependencies]
tokio = { version = "1", optional = true }
prometheus = { version = "0.13", optional = true }
#[cfg(feature = "metrics")]
fn record_latency(ms: u64) { /* ... */ }

#[cfg(not(feature = "metrics"))]
fn record_latency(_ms: u64) {}
构建脚本
// build.rs — runs before crate compilation.
fn main() {
    // Rerun when proto files change.
    println!("cargo:rerun-if-changed=proto/");
    // Link a system library.
    println!("cargo:rustc-link-lib=ssl");
}
有用的 Cargo 命令
cargo build --release          # optimized build
cargo clippy -- -D warnings    # lint (treat warnings as errors)
cargo fmt --check              # format check (CI)
cargo doc --no-deps --open     # generate and open docs
cargo audit                    # check dependencies for CVEs
cargo deny check               # license + advisory checks
cargo expand                   # show macro expansion

11. Rust 1.75+ 特性

Trait 中的异步 fn(在 1.75 中稳定)
// No longer requires #[async_trait] crate.
trait Fetcher {
    async fn fetch(&self, url: &str) -> anyhow::Result<Bytes>;
}
RPITIT——Trait 中返回位置的 Impl Trait(在 1.75 中稳定)
trait Source {
    fn items(&self) -> impl Iterator<Item = &str>;
}
let-else(在 1.65 中稳定)
let Ok(value) = parse_value(raw) else {
    return Err(ConfigError::InvalidValue);
};
std::io::ErrorKind——更丰富的变体(持续推进)
use std::io::ErrorKind;
match err.kind() {
    ErrorKind::NotFound => { /* ... */ }
    ErrorKind::PermissionDenied => { /* ... */ }
    _ => { /* ... */ }
}

12. 代码质量检查清单

完成 Rust 工作前:

  • cargo clippy -- -D warnings 通过,且没有警告。
  • cargo fmt --check 不产生任何更改。
  • cargo test(或 cargo nextest run)通过——所有测试均为绿色。
  • 所有 unsafe 块都有 // SAFETY: 注释。
  • 公共 API 项都有 /// 文档注释。
  • 库代码中没有 .unwrap(),测试代码除外。
  • 错误类型派生 Debug 并实现 std::error::Error。
  • 异步代码不会阻塞执行器线程。
  • 更改前后均对性能关键路径进行基准测试。

指派的 Agent

此技能由以下角色使用:

  • developer — Rust 特性实现与 Bug 修复
  • code-reviewer — Rust 专项代码审查模式
  • qa — Rust 测试策略(proptest、criterion、nextest)

记忆协议(强制)

开始前: 阅读 .claude/context/memory/learnings.md

完成后:

  • 发现新的 Rust 模式 -> .claude/context/memory/learnings.md
  • 遇到 Rust 专项问题或易错点 -> .claude/context/memory/issues.md
  • 架构或 crate 选择决策 -> .claude/context/memory/decisions.md

假设工作可能中断:你的上下文可能会重置。如果没有记录在记忆中,就等于没有发生。

评论

支持 Markdown