rust-advanced
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRust Advanced: Patterns, Conventions & Pitfalls
Rust高级:模式、约定与陷阱
This skill defines rules, conventions, and architectural decisions for building
production Rust applications. It is intentionally opinionated to prevent common
pitfalls and enforce patterns that scale.
For detailed API documentation of any crate mentioned here, use other appropriate
tools (documentation lookup, web search, etc.) — this skill focuses on how and
why to use these patterns, not full API surfaces.
本技能定义了构建生产级Rust应用的规则、约定和架构决策。它特意给出了倾向性建议,用于避免常见陷阱并推行可扩展的开发模式。
如需查找此处提到的任意crate的详细API文档,请使用其他合适的工具(文档查询、网页搜索等)——本技能聚焦于如何以及为什么使用这些模式,而非完整的API表层介绍。
Table of Contents
目录
Ownership & Borrowing Rules
所有权与借用规则
Interior mutability — decision flowchart
内部可变性——决策流程图
text
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 / borrowingtext
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 / borrowingSmart pointer selection
智能指针选型
| Type | When to use |
|---|---|
| Recursive types, large stack values, trait objects |
| Single-threaded shared ownership (trees, graphs) |
| Multi-threaded shared ownership |
| Sometimes borrowed, sometimes owned — avoid eager clones |
| Self-referential types, async futures |
| 类型 | 适用场景 |
|---|---|
| 递归类型、大栈值、trait对象 |
| 单线程共享所有权(树、图结构) |
| 多线程共享所有权 |
| 有时借用、有时拥有的场景——避免不必要的 eager 克隆 |
| 自引用类型、异步future |
The Cow rule
Cow使用规则
Accept or when a function sometimes modifies its input and
sometimes passes it through unchanged. This avoids allocating when no modification
is needed. Prefer in function arguments when you never need ownership.
Cow<str>Cow<[T]>&str当函数有时会修改输入、有时直接透传输入时,接受或类型,这样无需修改时可以避免内存分配。如果永远不需要获取输入的所有权,函数参数优先使用。
Cow<str>Cow<[T]>&strError Handling Strategy
错误处理策略
The golden rule: libraries use thiserror
, applications use anyhow
thiserroranyhow黄金规则:库用thiserror
,应用用anyhow
thiserroranyhow| Context | Crate | Why |
|---|---|---|
| Library crate | | Callers need to match on specific error variants |
| Binary / application | | Errors bubble up to user-facing messages with context |
| Internal modules | | Type-safe error variants for the parent module to handle |
| FFI boundary | Custom enum | Must map to C-compatible error codes |
| 场景 | 依赖Crate | 原因 |
|---|---|---|
| 库crate | | 调用方需要匹配具体的错误变体 |
| 二进制/应用 | | 错误会向上冒泡为带上下文的用户-facing 消息 |
| 内部模块 | | 为父模块提供类型安全的错误变体供处理 |
| FFI边界 | 自定义枚举 | 必须映射为C兼容的错误码 |
Required patterns
强制要求的模式
-
Always add context when propagating within application code:
?rustfs::read_to_string(path) .with_context(|| format!("failed to read config: {path}"))?; -
Usefor automatic conversions in library error enums:
#[from]rust#[derive(thiserror::Error, Debug)] pub enum DbError { #[error("connection failed: {0}")] Connection(#[from] std::io::Error), #[error("query failed: {reason}")] Query { reason: String }, } -
Prefercombinators over nested
Resultfor short chains:match,map,map_err,and_then.unwrap_or_else -
Neverin library code. Use
unwrap()only when the invariant is documented and provably upheld.expect()
-
在应用代码中用传播错误时必须添加上下文:
?rustfs::read_to_string(path) .with_context(|| format!("failed to read config: {path}"))?; -
库错误枚举中使用实现自动类型转换:
#[from]rust#[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 System Conventions
Trait系统约定
Trait objects vs generics — decision rule
Trait对象 vs 泛型——决策规则
text
Need runtime polymorphism (heterogeneous collection, plugin system)?
YES → dyn Trait (Box<dyn Trait> or &dyn Trait)
NO → impl Trait / generics (zero-cost, monomorphized)text
Need runtime polymorphism (heterogeneous collection, plugin system)?
YES → dyn Trait (Box<dyn Trait> or &dyn Trait)
NO → impl Trait / generics (zero-cost, monomorphized)Key patterns
核心模式
- Associated types over generics when there is exactly one natural
implementation per type (e.g., ).
Iterator::Item - Sealed traits when you need to prevent downstream crates from implementing your trait — essential for semver stability.
- Blanket implementations to extend functionality to all types satisfying a
bound (e.g., ).
impl<T: Display> ToString for T - Supertraits when your trait logically requires another trait's guarantees
(e.g., ).
trait Printable: Debug + Display
- 当每个类型仅有一种自然实现时,关联类型优先于泛型(例如)。
Iterator::Item - 当需要阻止下游crate实现你的trait时使用密封Trait——这对semver稳定性至关重要。
- 使用**通用实现(Blanket implementations)**为满足约束的所有类型扩展功能(例如)。
impl<T: Display> ToString for T - 当你的trait逻辑上依赖另一个trait的能力保证时使用超Trait(例如)。
trait Printable: Debug + Display
Object safety rules
对象安全规则
A trait is object-safe (can be used as ) only if:
dyn Trait- No methods return
Self - No methods have generic type parameters
- All methods take ,
self, or&self&mut self
If you need , use or return
manually — native async in traits is not yet object-safe.
dyn Trait + async#[async_trait]Box<dyn Future>一个trait是对象安全的(可以用作)当且仅当:
dyn Trait- 没有方法返回类型
Self - 没有方法带泛型类型参数
- 所有方法的接收器为、
self或&self&mut self
如果你需要,使用或者手动返回——原生trait异步方法目前还不支持对象安全。
dyn Trait + async#[async_trait]Box<dyn Future>Async Rust Rules
异步Rust规则
Runtime: Tokio is the default
运行时:默认使用Tokio
Use with and . For CPU-bound work
inside an async context, use or .
tokio#[tokio::main]#[tokio::test]tokio::task::spawn_blockingrayon使用配合和。对于异步上下文里的CPU密集型任务,使用或者。
tokio#[tokio::main]#[tokio::test]tokio::task::spawn_blockingrayonNative async traits — drop #[async_trait]
where possible
#[async_trait]原生异步Trait——尽可能停用#[async_trait]
#[async_trait]Since Rust 1.75, in traits works natively. Use native syntax unless
you need with async methods.
async fndyn Trait从Rust 1.75版本开始,trait中的已原生支持。除非你需要异步方法配合使用,否则优先使用原生语法。
async fndyn TraitThe Send/Sync rule
Send/Sync规则
Futures passed to must be . The #1 cause of non-Send
futures: holding a (or any type) across an point.
tokio::spawnSendMutexGuard!Send.awaitFix: drop the guard before awaiting, or scope the lock in a block:
rust
{
let mut guard = lock.lock().unwrap();
guard.push(42);
} // guard dropped
do_async_thing().await; // future is Send传递给的Future必须实现。非Send Future的首要原因:在点之间持有(或任何类型)。
tokio::spawnSend.awaitMutexGuard!Send修复方案: 在await之前释放guard,或者将锁的作用域限制在代码块内:
rust
{
let mut guard = lock.lock().unwrap();
guard.push(42);
} // guard dropped
do_async_thing().await; // future is SendCancellation safety — the most dangerous async footgun
取消安全——最危险的异步陷阱
Any future can be dropped at any point (especially in ).
Know which operations are cancel-safe:
.awaittokio::select!| Operation | Cancel-safe? |
|---|---|
| Yes |
| Yes |
| No |
| No |
For cancel-unsafe code: wrap in (dropping a does not
cancel the spawned task) or use for
cooperative cancellation.
tokio::spawnJoinHandletokio_util::sync::CancellationToken任何Future都可能在任意点被丢弃(尤其在中)。请明确哪些操作是取消安全的:
.awaittokio::select!| 操作 | 是否取消安全? |
|---|---|
| 是 |
| 是 |
| 否 |
| 否 |
对于非取消安全的代码:用包裹(丢弃不会取消已经 spawn 的任务),或者使用实现协作式取消。
tokio::spawnJoinHandletokio_util::sync::CancellationTokenStructured concurrency: use JoinSet
JoinSet结构化并发:使用JoinSet
JoinSetrust
let mut set = tokio::task::JoinSet::new();
for url in urls {
set.spawn(fetch(url));
}
while let Some(result) = set.join_next().await {
result??;
}rust
let mut set = tokio::task::JoinSet::new();
for url in urls {
set.spawn(fetch(url));
}
while let Some(result) = set.join_next().await {
result??;
}Type System Patterns
类型系统模式
Newtype — zero-cost domain types
新类型(Newtype)——零开销领域类型
Wrap primitives to create distinct types. Prevents mixing with :
UserIdOrderIdrust
struct UserId(u64);
struct OrderId(u64);
// fn process(user: UserId, order: OrderId) — compiler prevents swaps包装基础类型创建独立类型,避免把和混用:
UserIdOrderIdrust
struct UserId(u64);
struct OrderId(u64);
// fn process(user: UserId, order: OrderId) — compiler prevents swapsTypestate — compile-time state machine
类型状态(Typestate)——编译时状态机
Encode lifecycle states as type parameters. Invalid transitions become compile errors:
rust
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>
}将生命周期状态编码为类型参数,非法的状态转移会变成编译错误:
rust
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 generics — array sizes as type parameters
Const泛型——将数组大小作为类型参数
rust
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() }
}rust
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 variance
PhantomData 变体
| Marker | Variance | Use for |
|---|---|---|
| Covariant | "Owns" a T conceptually |
| Contravariant | Consumes T (rare) |
| Invariant | Must be exact type |
| Invariant | Raw pointer semantics |
| 标记 | 变体类型 | 适用场景 |
|---|---|---|
| 协变 | 概念上“拥有”一个T |
| 逆变 | 消费T(罕见) |
| 不变 | 必须是精确类型 |
| 不变 | 裸指针语义 |
Performance Decision Framework
性能决策框架
text
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 vecstext
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 vecsThe zero-cost rule
零开销规则
Iterator chains () compile to the same code as hand-written
loops — prefer them for readability. But stateful iterator chains can block
auto-vectorization; see for SIMD details.
filter().map().sum()references/performance.md迭代器链()编译后和手写循环的代码完全一致——优先使用迭代器保证可读性。但有状态的迭代器链可能会阻塞自动向量化,SIMD相关细节请参考。
filter().map().sum()references/performance.mdUnsafe Policy
Unsafe代码规范
- Minimize scope — wrap only the minimum number of lines in .
unsafe {} - Mandatory comment on every
// SAFETY:block explaining why the invariants are upheld.unsafe - Prefer safe abstractions — casts,
as,bytemuck::castoverfrom_raw_parts. Usetransmuteonly as last resort with turbofish syntax.transmute - FFI boundary rule: generate bindings with , wrap in a thin safe Rust API, document every invariant.
bindgen - Never use to bypass the borrow checker. If you think you need to, redesign the data structure.
unsafe
- 最小化作用域——仅将最少的必要代码包裹在中。
unsafe {} - 每个块必须附带
unsafe注释,说明为何代码满足不变量要求。// SAFETY: - 优先使用安全抽象——优先用转换、
as、bytemuck::cast,而非from_raw_parts。仅在万不得已时使用带turbofish语法的transmute。transmute - FFI边界规则: 用生成绑定,封装为精简的安全Rust API,为每个不变量添加文档说明。
bindgen - 永远不要用绕过借用检查器。如果你认为需要这么做,请重新设计数据结构。
unsafe
Common Pitfalls
常见陷阱
-
Holdingacross
MutexGuard— makes the future.await, breaks!Send. Scope the lock in a block before awaiting.tokio::spawn -
double borrow panic —
RefCellpanics if any borrow is live. Useborrow_mut()when borrow lifetimes aren't fully controlled.try_borrow_mut() -
deadlock — Rust's
Mutexis non-reentrant. Never lock the same mutex twice on one thread. Acquire multiple locks in consistent order.Mutex -
vs
collect::<Vec<Result<T, E>>>()— the second form fails fast on first error and is almost always what you want.collect::<Result<Vec<T>, E>>() -
Acceptinginstead of
&String—&strauto-derefs to&Stringbut not vice versa. Always accept&strin function signatures.&str -
in library code — crashes the caller. Use
unwrap()with proper error types, or?with documented invariant.expect() -
Forgettingon
#[must_use]-returning functions — callers may silently ignore errors. The compiler warns, but custom types need the attribute.Result -
Usingin async code — blocks the executor thread. Use
std::sync::Mutexfor async contexts.tokio::sync::Mutex -
in hot loops — allocates each iteration. Pre-allocate with
String::fromor useString::with_capacity().Cow<str> -
Ignoring cancellation safety in— the non-winning future is dropped. Cancel-unsafe operations lose data silently.
select! -
as first instinct — usually a sign of fighting the borrow checker. Restructure ownership or use references first.
clone() -
instead of proper error enum — loses the ability to match on specific variants. Use
Box<dyn Error>for structured errors.thiserror
-
在之间持有
.await——会导致Future为MutexGuard,破坏!Send的使用。请在await之前将锁的作用域限制在独立代码块内。tokio::spawn -
双重借用panic——如果存在任何活跃借用时调用
RefCell会触发panic。当借用生命周期不可控时使用borrow_mut()。try_borrow_mut() -
死锁——Rust的
Mutex不可重入,永远不要在同一个线程中对同一个mutex加锁两次。获取多个锁时遵循一致的顺序。Mutex -
和
collect::<Vec<Result<T, E>>>()的区别——第二种形式会在遇到第一个错误时快速失败,几乎总是你需要的用法。collect::<Result<Vec<T>, E>>() -
接受而非
&String作为参数——&str可以自动解引用为&String,但反过来不行。函数签名中永远优先接受&str。&str -
库代码中使用——会导致调用方崩溃。请使用
unwrap()配合合适的错误类型,或者搭配有文档说明的不变量使用?。expect() -
返回的函数忘记加
Result——调用方可能会静默忽略错误。编译器会为原生Result类型报警,但自定义类型需要显式添加该属性。#[must_use] -
在异步代码中使用——会阻塞执行器线程。异步上下文请使用
std::sync::Mutex。tokio::sync::Mutex -
热循环中使用——每次迭代都会分配内存。优先使用
String::from预分配,或者使用String::with_capacity()。Cow<str> -
中忽略取消安全问题——未被选中的Future会被丢弃,非取消安全的操作会静默丢失数据。
select! -
第一反应就用——通常是和借用检查器对抗的信号。优先调整所有权结构或者使用引用。
clone() -
用代替合适的错误枚举——会丢失匹配具体错误变体的能力。结构化错误请使用
Box<dyn Error>。thiserror
Reference Files
参考文件
Read the relevant reference file when working with a specific topic:
| File | When to read |
|---|---|
| Interior mutability, smart pointers, Cow, Pin, lifetime tricks |
| Trait objects, sealed traits, blanket impls, HRTB, variance |
| thiserror v2, anyhow, Result combinators, error design |
| Tokio runtime, cancellation, JoinSet, Send/Sync, select! |
| Zero-cost, SIMD, arena allocation, rayon, cache optimization |
| Unsafe superpowers, FFI with bindgen, transmute, raw pointers |
| Declarative macros, proc macros, derive macros, syn/quote |
| Newtype, typestate, PhantomData, const generics, builder |
处理特定主题时请阅读对应的参考文件:
| 文件 | 适用场景 |
|---|---|
| 内部可变性、智能指针、Cow、Pin、生命周期技巧 |
| Trait对象、密封Trait、通用实现、HRTB、变体 |
| thiserror v2、anyhow、Result组合子、错误设计 |
| Tokio运行时、取消、JoinSet、Send/Sync、select! |
| 零开销、SIMD、 arena 分配、rayon、缓存优化 |
| Unsafe能力、用bindgen做FFI、transmute、裸指针 |
| 声明宏、过程宏、派生宏、syn/quote |
| 新类型、类型状态、PhantomData、const泛型、构建器 |