mcpField
R
rust-advanced

rust-advanced

github.com/trancong12102开发748 次安装5 周前更新
代码仓库

关于 rust-advanced

面向 Rust 应用开发者的进阶技能,提供所有权、错误处理、异步、类型设计与性能优化等生产实践,并强调安全与常见陷阱。

主要功能

  1. 01所有权与智能指针选择
  2. 02库与应用的错误处理规范
  3. 03Tokio 异步与取消安全
  4. 04类型系统和生命周期模式
  5. 05unsafe 与 FFI 安全准则
  6. 06性能分析与优化决策

适用场景

  1. 01设计共享状态和借用策略
  2. 02为库或应用规划错误类型
  3. 03排查异步任务的 Send 与取消问题
  4. 04评估性能瓶颈并选择优化方法

安装

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

$npx skills add https://github.com/trancong12102/agentskills --skill rust-advanced

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

包含的文件

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

references/
async-rust.md
error-handling.md
macros.md
ownership.md
performance.md
traits.md
type-patterns.md
unsafe-ffi.md

详细介绍

Rust 进阶:模式、约定与陷阱

本技能定义了构建生产级 Rust 应用的规则、约定和架构决策。它有意采用明确的观点,以避免常见陷阱并确保模式具有良好的可扩展性。

如需查询此处提及的任何 crate 的详细 API 文档,请使用其他合适的工具(文档查找、网络搜索等)——本技能侧重于这些模式的使用方式和原因,而非完整的 API 范围。

所有权与借用规则

内部可变性——决策流程图
Need shared mutation?
  YES → Single-threaded or multi-threaded?
    Single-threaded → Is T: Copy?
      YES → Cell<T> (zero overhead, no borrow tracking)
      NO  → RefCell<T> (runtime borrow checking, panics on violation)
    Multi-threaded → High contention?
      NO  → Arc<Mutex<T>> (simple, correct)
      YES → Arc<RwLock<T>> (many readers, few writers)
             or lock-free types (crossbeam, atomic)
  NO → Use normal ownership / borrowing
智能指针选择
类型使用场景
Box<T>递归类型、大型栈值、trait 对象
Rc<T>单线程共享所有权(树、图)
Arc<T>多线程共享所有权
Cow<'a, T>有时借用、有时拥有——避免急切克隆
Pin<Box<T>>自引用类型、异步 future
Cow 规则

当函数有时会修改输入、有时会原样传递输入时,接受 Cow<str> 或 Cow<[T]>。这样在不需要修改时可以避免分配。若函数参数始终不需要所有权,则优先使用 &str。


错误处理策略

黄金规则:库使用 thiserror,应用使用 anyhow
场景Crate原因
库 cratethiserror调用方需要匹配特定错误变体
二进制程序/应用anyhow错误会向上传递,并附带上下文显示给用户
内部模块thiserror为父模块处理提供类型安全的错误变体
FFI 边界自定义枚举必须映射为兼容 C 的错误代码
必需模式
  1. 在应用代码中使用 ? 传递错误时,始终添加上下文:

    fs::read_to_string(path)
        .with_context(|| format!("failed to read config: {path}"))?;
    
  2. 在库的错误枚举中使用 #[from] 进行自动转换:

    #[derive(thiserror::Error, Debug)]
    pub enum DbError {
        #[error("connection failed: {0}")]
        Connection(#[from] std::io::Error),
        #[error("query failed: {reason}")]
        Query { reason: String },
    }
    
  3. 对于简短调用链,优先使用 Result 组合器,而不是嵌套的 match: map、map_err、and_then、unwrap_or_else。

  4. 绝不要在库代码中使用 unwrap()。 仅当不变量已记录且能够证明始终成立时,才使用 expect()。


Trait 系统约定

Trait 对象与泛型——决策规则
Need runtime polymorphism (heterogeneous collection, plugin system)?
  YES → dyn Trait (Box<dyn Trait> or &dyn Trait)
  NO  → impl Trait / generics (zero-cost, monomorphized)
关键模式
  • 当每种类型恰好有一种自然实现时,优先使用关联类型而非泛型(例如,Iterator::Item)。
  • 需要禁止下游 crate 实现你的 trait 时,使用封闭 trait——这对 semver 稳定性至关重要。
  • 使用通用实现,为所有满足约束的类型扩展功能(例如,impl<T: Display> ToString for T)。
  • 当你的 trait 在逻辑上要求另一个 trait 提供保障时,使用超 trait(例如,trait Printable: Debug + Display)。
对象安全规则

Trait 只有满足以下条件时才是对象安全的(可用作 dyn Trait):

  • 没有方法返回 Self
  • 没有方法具有泛型类型参数
  • 所有方法都以 self、&self 或 &mut self 为接收者

如果需要 dyn Trait + async,请使用 #[async_trait],或手动返回 Box<dyn Future>——trait 中的原生 async 尚不具备对象安全性。


Rust 异步规则

运行时:默认使用 Tokio

使用 tokio,并搭配 #[tokio::main] 和 #[tokio::test]。在异步上下文中执行 CPU 密集型任务时,使用 tokio::task::spawn_blocking 或 rayon。

原生异步 trait——尽可能移除 #[async_trait]

从 Rust 1.75 开始,trait 中的 async fn 已可原生使用。除非需要让 dyn Trait 包含异步方法,否则请使用原生语法。

Send/Sync 规则

传递给 tokio::spawn 的 Future 必须满足 Send。导致 Future 不满足 Send 的首要原因:在 .await 处仍持有 MutexGuard(或任何 !Send 类型)。

**修复方法:**在等待前释放 guard,或将锁的作用域限定在一个代码块中:

{
    let mut guard = lock.lock().unwrap();
    guard.push(42);
} // guard dropped
do_async_thing().await; // future is Send
取消安全性——最危险的异步陷阱

任何 Future 都可能在任意 .await 处被丢弃(尤其是在 tokio::select! 中)。了解哪些操作支持取消安全:

操作取消安全?
mpsc::Receiver::recv是
AsyncReadExt::read是
AsyncWriteExt::write_all否
AsyncBufReadExt::read_line否

对于取消不安全的代码:将其包装在 tokio::spawn 中(丢弃 JoinHandle 不会取消已生成的任务),或使用 tokio_util::sync::CancellationToken 实现协作式取消。

结构化并发:使用 JoinSet
let mut set = tokio::task::JoinSet::new();
for url in urls {
    set.spawn(fetch(url));
}
while let Some(result) = set.join_next().await {
    result??;
}

类型系统模式

Newtype——零成本领域类型

包装基本类型以创建不同的类型,防止混淆 UserId 和 OrderId:

struct UserId(u64);
struct OrderId(u64);
// fn process(user: UserId, order: OrderId) — compiler prevents swaps
Typestate——编译时状态机

将生命周期状态编码为类型参数。无效的状态转换会导致编译错误:

struct Connection<S> { socket: TcpStream, _state: PhantomData<S> }
struct Disconnected;
struct Connected;

impl Connection<Disconnected> {
    fn connect(self) -> Result<Connection<Connected>> { ... }
}
impl Connection<Connected> {
    fn send(&self, data: &[u8]) -> Result<()> { ... }
    // send() is unavailable on Connection<Disconnected>
}
Const 泛型——将数组大小作为类型参数
struct Matrix<const ROWS: usize, const COLS: usize> {
    data: [[f64; COLS]; ROWS],
}
impl<const N: usize> Matrix<N, N> {
    fn trace(&self) -> f64 { (0..N).map(|i| self.data[i][i]).sum() }
}
PhantomData 方差
标记方差用途
PhantomData<T>协变概念上“拥有”一个 T
PhantomData<fn(T)>逆变消费 T(罕见)
PhantomData<fn(T) -> T>不变必须是确切类型
PhantomData<*const T>不变原始指针语义

性能决策框架

Is this a hot path (profiled, not guessed)?
  NO  → Write clear, idiomatic code. Don't optimize.
  YES → Which bottleneck?
    CPU-bound computation → rayon::par_iter() for data parallelism
    Many small allocations → Arena allocator (bumpalo)
    Iterator chain not vectorizing → Check for stateful dependencies,
      use fold/try_fold, or restructure as plain slice iteration
    Cache misses → #[repr(C)] + align, struct-of-arrays layout
    Heap allocation → Box<[T]> instead of Vec<T> when size is fixed,
      stack allocation for small types, SmallVec for usually-small vecs
零成本规则

迭代器链(filter().map().sum())编译后与手写循环生成的代码相同——为提高可读性,优先使用迭代器链。但有状态的迭代器链可能会阻碍自动向量化;SIMD 详情请参阅 references/performance.md。


Unsafe 策略

  1. 尽量缩小作用范围——只将最少行数的代码放在 unsafe {} 中。
  2. 每个 unsafe 块都必须附带 // SAFETY: 注释,说明如何满足不变量。
  3. 优先使用安全抽象——优先使用 as 转换、bytemuck::cast 和 from_raw_parts,而非 transmute。只有在不得已时才使用 transmute,并采用 turbofish 语法。
  4. **FFI 边界规则:**使用 bindgen 生成绑定,将其包装在精简的安全 Rust API 中,并记录每个不变量。
  5. **绝不要使用 unsafe 绕过借用检查器。**如果你认为有必要这么做,请重新设计数据结构。

常见陷阱

  1. 在 .await 期间持有 MutexGuard——这会使 future 变为 !Send,导致 tokio::spawn 失效。应在等待前将锁限定在一个代码块中。

  2. RefCell 重复借用导致 panic——只要存在任何未结束的借用,borrow_mut() 就会 panic。若无法完全控制借用的生命周期,请使用 try_borrow_mut()。

  3. Mutex 死锁——Rust 的 Mutex 不可重入。切勿在同一线程上对同一个互斥锁加锁两次。按一致的顺序获取多个锁。

  4. collect::<Vec<Result<T, E>>>() 与 collect::<Result<Vec<T>, E>>()——第二种形式会在遇到第一个错误时立即失败,几乎总是你想要的形式。

  5. 接受 &String 而非 &str——&String 会自动解引用为 &str,反之则不然。函数签名中始终接受 &str。

  6. 在库代码中使用 unwrap()——这会导致调用方崩溃。请使用带有适当错误类型的 ?,或使用附有文档说明的不变量的 expect()。

  7. 忘记在返回 Result 的函数上标注 #[must_use]——调用方可能会悄无声息地忽略错误。编译器会发出警告,但自定义类型需要此属性。

  8. 在异步代码中使用 std::sync::Mutex——这会阻塞执行器线程。在异步上下文中请使用 tokio::sync::Mutex。

  9. 在热点循环中使用 String::from——每次迭代都会分配内存。请使用 String::with_capacity() 预先分配,或使用 Cow<str>。

  10. 在 select! 中忽略取消安全性——未获胜的 future 会被丢弃。不具备取消安全性的操作会悄无声息地丢失数据。

  11. 第一反应就是 clone()——这通常表明你在与借用检查器较劲。应先调整所有权结构或使用引用。

  12. 使用 Box<dyn Error> 而非合适的错误枚举——这会失去对特定变体进行匹配的能力。请使用 thiserror 定义结构化错误。


参考文件

处理特定主题时,请阅读相关参考文件:

文件阅读时机
references/ownership.md内部可变性、智能指针、Cow、Pin、生命周期技巧
references/traits.mdtrait 对象、密封 trait、通用实现、HRTB、变型
references/error-handling.mdthiserror v2、anyhow、Result 组合器、错误设计
references/async-rust.mdTokio 运行时、取消、JoinSet、Send/Sync、select!
references/performance.md零开销、SIMD、Arena 分配、rayon、缓存优化
references/unsafe-ffi.mdUnsafe 超能力、使用 bindgen 进行 FFI、transmute、原始指针
references/macros.md声明式宏、过程宏、派生宏、syn/quote
references/type-patterns.mdNewtype、类型状态、PhantomData、const 泛型、构建器

评论

支持 Markdown