Rust专家Skill rust-expert

这是一个关于Rust编程语言的专家级技能,涵盖所有权、借用、生命周期、异步Tokio模式、错误处理、trait系统、性能优化、测试和生产系统开发。关键词:Rust编程, 所有权, 借用, 异步, Tokio, 错误处理, 性能优化, 测试, 生产系统, 代码质量

后端开发 0 次安装 0 次浏览 更新于 3/10/2026

名称: rust-expert 描述: Rust编程专家,包括所有权、借用、生命周期、异步Tokio模式、错误处理、trait系统、性能优化、测试和生产系统开发 版本: 2.0.0 模型: sonnet 调用者: both 用户可调用: true 工具: [读, 写, 编辑, Bash, Grep, Glob] 已验证: true 最后验证时间: 2026-02-19T00:00:00.000Z

Rust 专家

应用惯用的Rust模式,提供强大的安全性、性能和可维护性保证。

核心原则

  • 通过类型系统建模正确性;使无效状态不可表示。
  • 偏好明确而非隐式;在每个签名中体现所有权意图。
  • 编写最小化、零成本的抽象——没有不必要的分配或间接。
  • 测试行为而非实现;优先在crate边界进行集成测试。

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

所有权模式

// 当值被消耗时优先移动;当值被共享时优先借用。
fn process(data: Vec<u8>) -> Vec<u8> { /* 消耗 */ }
fn inspect(data: &[u8]) -> usize { data.len() /* 借用 */ }

// 仅在必要时克隆并明确记录。
// 反模式:使用clone()来静音借用检查器错误——应修复设计。

借用规则(在设计时强制执行)

  • 一次只能有一个可变引用或任意数量的不可变引用。
  • 引用不能超过其所指向值的生命周期。
  • 使用 Cow<'a, T> 当您需要借用或拥有语义而不强制分配时。
use std::borrow::Cow;

fn normalize(s: &str) -> Cow<str> {
    if s.contains(' ') {
        Cow::Owned(s.replace(' ', "_"))
    } else {
        Cow::Borrowed(s)
    }
}

生命周期模式

// 显式生命周期注释:仅当编译器无法推断时使用。
struct Parser<'input> {
    source: &'input str,
    pos: usize,
}

impl<'input> Parser<'input> {
    fn next_token(&mut self) -> &'input str {
        // 返回原始输入的切片——生命周期将结果绑定到源。
        &self.source[self.pos..]
    }
}

// RPIT(返回位置impl Trait)避免在许多情况下的生命周期噪音。
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(库Crates)

use thiserror::Error;

#[derive(Debug, Error)]
pub enum ConfigError {
    #[error("缺少必需字段: {field}")]
    MissingField { field: &'static str },
    #[error("字段 {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!("从 {path} 读取配置"))?;
    toml::from_str(&raw).context("解析配置TOML")
}

错误传播规则

  • 绝不在库代码中使用 .unwrap();仅在测试和 main() 中使用 .expect(),当不变量自明时。
  • 在每个错误边界添加 .context() / .with_context() 以保留调用链。
  • 在crate边界映射错误——不要在公共API中泄露内部错误类型。

3. Trait 系统

泛型 vs Trait 对象

// 当类型在编译时已知时,优先使用泛型(单态化、零开销)。
fn serialize<S: Serialize>(value: &S) -> Vec<u8> { /* ... */ }

// 仅当需要异构集合或运行时分发时使用 dyn Trait。
fn handlers() -> Vec<Box<dyn EventHandler>> { /* ... */ }

Where 子句

// 当边界复杂时,优先使用where子句以提高可读性。
fn merge<K, V>(a: HashMap<K, V>, b: HashMap<K, V>) -> HashMap<K, V>
where
    K: Eq + Hash,
    V: Clone,
{ /* ... */ }

覆盖实现和孤儿规则

  • 为您的类型实现标准trait(Display, From, Iterator)。
  • 尊重孤儿规则:只能为本地类型实现外来的trait。
  • 使用新类型模式绕过孤儿限制。
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中(RPITIT)

// 从Rust 1.75起稳定——不再需要async-trait宏。
trait DataSource {
    async fn fetch(&self, id: u64) -> anyhow::Result<Record>;
}

// 返回位置impl Trait在Trait中(RPITIT)——也在1.75中稳定。
trait Transformer {
    fn transform(&self, input: &str) -> impl Iterator<Item = String>;
}

4. 异步 / Await 与 Tokio

Tokio 运行时设置

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 默认:多线程运行时(所有CPU核心)。
    Ok(())
}

// 单线程运行时用于仅I/O的工作负载。
#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> { Ok(()) }

任务生成

use tokio::task;

// 生成一个非阻塞异步任务。
let handle = task::spawn(async move {
    fetch_data(url).await
});
let result = handle.await?; // 传播JoinError

// CPU密集型工作必须转到阻塞线程池——绝不阻塞异步运行时。
let result = task::spawn_blocking(|| {
    expensive_cpu_computation()
}).await?;

通道

use tokio::sync::{mpsc, oneshot, broadcast, watch};

// mpsc: 生产者 → 消费者流水线。
let (tx, mut rx) = mpsc::channel::<Bytes>(1024);

// oneshot: 请求 / 响应模式。
let (resp_tx, resp_rx) = oneshot::channel::<Result<Record>>();

// broadcast: 扇出到多个订阅者。
let (tx, _rx) = broadcast::channel::<Event>(16);

// watch: 最新值语义(配置重载、健康状态)。
let (tx, rx) = watch::channel(Config::default());

select! 和取消

use tokio::select;
use tokio_util::sync::CancellationToken;

async fn worker(token: CancellationToken) {
    select! {
        _ = token.cancelled() => {
            // 结构化取消——始终处理关闭路径。
        }
        result = do_work() => {
            // 正常完成。
        }
    }
}

结构化并发

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("任务恐慌"));
    }
    results
}

异步反模式

  • 在异步上下文中使用 std::thread::sleep——应使用 tokio::time::sleep
  • .await 中持有 MutexGuard——应使用 tokio::sync::Mutex
  • 在异步线程上进行阻塞I/O(文件读取、DNS)——应使用 spawn_blockingtokio::fs
  • 无界通道——始终设置容量限制。

5. 常见Crates

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 — 异步数据库

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

// 使用 std::simd(nightly)或 portable-simd crate 进行显式向量化。
// 先进行性能分析——LLVM自动向量化大多数迭代器链。
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(安装:cargo install flamegraph)
cargo flamegraph --bin my-app

# perf stat 用于CPU计数器(Linux)
perf stat cargo run --release

# heaptrack 用于堆分配分析(Linux)
heaptrack cargo run --release

# criterion 用于微基准测试
cargo bench

分配意识

// 当大小已知时预分配。
let mut v = Vec::with_capacity(expected_len);

// 字符串构建:使用 write! 到预分配的 String 中。
use std::fmt::Write;
let mut s = String::with_capacity(256);
write!(s, "id={}", id)?;

// 避免在热路径中使用 format!()——优先使用直接 write!() 或 push_str()。

7. 测试

单元测试

#[cfg(test)]
mod tests {
    use super::*;

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

    #[test]
    fn parse_invalid_port_returns_error() {
        let result = Config::from_str("[server]
port = -1");
        assert!(result.is_err());
    }
}

集成测试

// tests/integration/server.rs — 测试完整公共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);
    }
}

异步测试

#[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());
}

测试约定

  • 每个测试函数测试一个行为。
  • 使用描述性名称:<函数>_<场景>_<预期>
  • 仅模拟外部I/O边界(HTTP、文件系统、数据库)。
  • 在开发期间运行 cargo test -- --nocapture 以获取诊断输出。
  • 在CI中运行 cargo nextest run 以进行更快的并行测试执行。

8. 不安全Rust

何时使用

  • FFI边界(调用C函数、导出到C)。
  • 低级内存映射或SIMD intrinsic,当安全抽象无法达到时。
  • 性能关键代码,其中安全替代方案具有可测量的开销(通过性能分析证明)。

指南

/// # 安全性
///
/// - `ptr` 必须非空且对齐于 `T`。
/// - `ptr` 处的内存必须对 `T` 类型的 `len` 个元素有效。
/// - 调用者必须确保内存不存在其他可变引用。
pub unsafe fn from_raw_parts<T>(ptr: *const T, len: usize) -> &'static [T] {
    std::slice::from_raw_parts(ptr, len)
}
  • 每个 unsafe 块必须有一个 // 安全性: 注释解释为何是安全的。
  • 最小化 unsafe 的范围——立即包装在安全抽象中。
  • 当整个函数不安全时,优先使用 unsafe fn 而非在安全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("无空字节");
    // 安全性:cstr 是有效的、以空终止的C字符串。
    unsafe { strlen(cstr.as_ptr()) }
}

cbindgen / bindgen

  • 使用 cbindgen 从Rust公共API生成C头文件。
  • 使用 bindgen 从C头文件生成Rust绑定。
  • 始终通过CI运行以检测API漂移。

10. 构建系统

Cargo Workspaces

# Cargo.toml(工作空间根目录)
[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 — 在crate编译前运行。
fn main() {
    // 当proto文件更改时重新运行。
    println!("cargo:rerun-if-changed=proto/");
    // 链接系统库。
    println!("cargo:rustc-link-lib=ssl");
}

有用的Cargo命令

cargo build --release          # 优化构建
cargo clippy -- -D warnings    # 代码检查(将警告视为错误)
cargo fmt --check              # 格式化检查(CI)
cargo doc --no-deps --open     # 生成并打开文档
cargo audit                    # 检查依赖项的CVE
cargo deny check               # 许可证 + 咨询检查
cargo expand                   # 显示宏扩展

11. Rust 1.75+ 特性

异步函数在Trait中(稳定1.75)

// 不再需要 #[async_trait] crate。
trait Fetcher {
    async fn fetch(&self, url: &str) -> anyhow::Result<Bytes>;
}

RPITIT — 返回位置impl Trait在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 块都有 // 安全性: 注释。
  • [ ] 公共API项有 /// 文档注释。
  • [ ] 库代码中无 .unwrap(),测试除外。
  • [ ] 错误类型派生 Debug 并实现 std::error::Error
  • [ ] 异步代码不阻塞执行器线程。
  • [ ] 性能关键路径在更改前后进行基准测试。

分配的智能体

此技能由以下使用:

  • 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

假设中断:您的上下文可能重置。如果不在内存中,则未发生。