关于 rust-advanced
面向 Rust 应用开发者的进阶技能,提供所有权、错误处理、异步、类型设计与性能优化等生产实践,并强调安全与常见陷阱。
主要功能
- 01所有权与智能指针选择
- 02库与应用的错误处理规范
- 03Tokio 异步与取消安全
- 04类型系统和生命周期模式
- 05unsafe 与 FFI 安全准则
- 06性能分析与优化决策
适用场景
- 01设计共享状态和借用策略
- 02为库或应用规划错误类型
- 03排查异步任务的 Send 与取消问题
- 04评估性能瓶颈并选择优化方法
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trancong12102/ agentskills --skill rust-advanced安装后在 Claude Code 中输入/rust-advanced确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/async-rust.mderror-handling.mdmacros.mdownership.mdperformance.mdtraits.mdtype-patterns.mdunsafe-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 | 原因 |
|---|---|---|
| 库 crate | thiserror | 调用方需要匹配特定错误变体 |
| 二进制程序/应用 | anyhow | 错误会向上传递,并附带上下文显示给用户 |
| 内部模块 | thiserror | 为父模块处理提供类型安全的错误变体 |
| FFI 边界 | 自定义枚举 | 必须映射为兼容 C 的错误代码 |
必需模式
-
在应用代码中使用
?传递错误时,始终添加上下文:fs::read_to_string(path) .with_context(|| format!("failed to read config: {path}"))?; -
在库的错误枚举中使用
#[from]进行自动转换:#[derive(thiserror::Error, Debug)] pub enum DbError { #[error("connection failed: {0}")] Connection(#[from] std::io::Error), #[error("query failed: {reason}")] Query { reason: String }, } -
对于简短调用链,优先使用
Result组合器,而不是嵌套的match:map、map_err、and_then、unwrap_or_else。 -
绝不要在库代码中使用
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 策略
- 尽量缩小作用范围——只将最少行数的代码放在
unsafe {}中。 - 每个
unsafe块都必须附带// SAFETY:注释,说明如何满足不变量。 - 优先使用安全抽象——优先使用
as转换、bytemuck::cast和from_raw_parts,而非transmute。只有在不得已时才使用transmute,并采用 turbofish 语法。 - **FFI 边界规则:**使用
bindgen生成绑定,将其包装在精简的安全 Rust API 中,并记录每个不变量。 - **绝不要使用
unsafe绕过借用检查器。**如果你认为有必要这么做,请重新设计数据结构。
常见陷阱
-
在
.await期间持有MutexGuard——这会使 future 变为!Send,导致tokio::spawn失效。应在等待前将锁限定在一个代码块中。 -
RefCell重复借用导致 panic——只要存在任何未结束的借用,borrow_mut()就会 panic。若无法完全控制借用的生命周期,请使用try_borrow_mut()。 -
Mutex死锁——Rust 的Mutex不可重入。切勿在同一线程上对同一个互斥锁加锁两次。按一致的顺序获取多个锁。 -
collect::<Vec<Result<T, E>>>()与collect::<Result<Vec<T>, E>>()——第二种形式会在遇到第一个错误时立即失败,几乎总是你想要的形式。 -
接受
&String而非&str——&String会自动解引用为&str,反之则不然。函数签名中始终接受&str。 -
在库代码中使用
unwrap()——这会导致调用方崩溃。请使用带有适当错误类型的?,或使用附有文档说明的不变量的expect()。 -
忘记在返回
Result的函数上标注#[must_use]——调用方可能会悄无声息地忽略错误。编译器会发出警告,但自定义类型需要此属性。 -
在异步代码中使用
std::sync::Mutex——这会阻塞执行器线程。在异步上下文中请使用tokio::sync::Mutex。 -
在热点循环中使用
String::from——每次迭代都会分配内存。请使用String::with_capacity()预先分配,或使用Cow<str>。 -
在
select!中忽略取消安全性——未获胜的 future 会被丢弃。不具备取消安全性的操作会悄无声息地丢失数据。 -
第一反应就是
clone()——这通常表明你在与借用检查器较劲。应先调整所有权结构或使用引用。 -
使用
Box<dyn Error>而非合适的错误枚举——这会失去对特定变体进行匹配的能力。请使用thiserror定义结构化错误。
参考文件
处理特定主题时,请阅读相关参考文件:
| 文件 | 阅读时机 |
|---|---|
references/ownership.md | 内部可变性、智能指针、Cow、Pin、生命周期技巧 |
references/traits.md | trait 对象、密封 trait、通用实现、HRTB、变型 |
references/error-handling.md | thiserror v2、anyhow、Result 组合器、错误设计 |
references/async-rust.md | Tokio 运行时、取消、JoinSet、Send/Sync、select! |
references/performance.md | 零开销、SIMD、Arena 分配、rayon、缓存优化 |
references/unsafe-ffi.md | Unsafe 超能力、使用 bindgen 进行 FFI、transmute、原始指针 |
references/macros.md | 声明式宏、过程宏、派生宏、syn/quote |
references/type-patterns.md | Newtype、类型状态、PhantomData、const 泛型、构建器 |