rust-advanced

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Rust 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 / borrowing
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 / borrowing

Smart pointer selection

智能指针选型

TypeWhen to use
Box<T>
Recursive types, large stack values, trait objects
Rc<T>
Single-threaded shared ownership (trees, graphs)
Arc<T>
Multi-threaded shared ownership
Cow<'a, T>
Sometimes borrowed, sometimes owned — avoid eager clones
Pin<Box<T>>
Self-referential types, async futures
类型适用场景
Box<T>
递归类型、大栈值、trait对象
Rc<T>
单线程共享所有权(树、图结构)
Arc<T>
多线程共享所有权
Cow<'a, T>
有时借用、有时拥有的场景——避免不必要的 eager 克隆
Pin<Box<T>>
自引用类型、异步future

The Cow rule

Cow使用规则

Accept
Cow<str>
or
Cow<[T]>
when a function sometimes modifies its input and sometimes passes it through unchanged. This avoids allocating when no modification is needed. Prefer
&str
in function arguments when you never need ownership.

当函数有时会修改输入、有时直接透传输入时,接受
Cow<str>
Cow<[T]>
类型,这样无需修改时可以避免内存分配。如果永远不需要获取输入的所有权,函数参数优先使用
&str

Error Handling Strategy

错误处理策略

The golden rule: libraries use
thiserror
, applications use
anyhow

黄金规则:库用
thiserror
,应用用
anyhow

ContextCrateWhy
Library crate
thiserror
Callers need to match on specific error variants
Binary / application
anyhow
Errors bubble up to user-facing messages with context
Internal modules
thiserror
Type-safe error variants for the parent module to handle
FFI boundaryCustom enumMust map to C-compatible error codes
场景依赖Crate原因
库crate
thiserror
调用方需要匹配具体的错误变体
二进制/应用
anyhow
错误会向上冒泡为带上下文的用户-facing 消息
内部模块
thiserror
为父模块提供类型安全的错误变体供处理
FFI边界自定义枚举必须映射为C兼容的错误码

Required patterns

强制要求的模式

  1. Always add context when propagating with
    ?
    in application code:
    rust
    fs::read_to_string(path)
        .with_context(|| format!("failed to read config: {path}"))?;
  2. Use
    #[from]
    for automatic conversions
    in library error enums:
    rust
    #[derive(thiserror::Error, Debug)]
    pub enum DbError {
        #[error("connection failed: {0}")]
        Connection(#[from] std::io::Error),
        #[error("query failed: {reason}")]
        Query { reason: String },
    }
  3. Prefer
    Result
    combinators
    over nested
    match
    for short chains:
    map
    ,
    map_err
    ,
    and_then
    ,
    unwrap_or_else
    .
  4. Never
    unwrap()
    in library code.
    Use
    expect()
    only when the invariant is documented and provably upheld.

  1. 在应用代码中用
    ?
    传播错误时必须添加上下文
    rust
    fs::read_to_string(path)
        .with_context(|| format!("failed to read config: {path}"))?;
  2. 库错误枚举中使用
    #[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 },
    }
  3. 短调用链优先使用
    Result
    组合子
    而非嵌套
    match
    :包括
    map
    map_err
    and_then
    unwrap_or_else
  4. 库代码中永远不要使用
    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
dyn Trait
) only if:
  • No methods return
    Self
  • No methods have generic type parameters
  • All methods take
    self
    ,
    &self
    , or
    &mut self
If you need
dyn Trait + async
, use
#[async_trait]
or return
Box<dyn Future>
manually — native async in traits is not yet object-safe.

一个trait是对象安全的(可以用作
dyn Trait
)当且仅当:
  • 没有方法返回
    Self
    类型
  • 没有方法带泛型类型参数
  • 所有方法的接收器为
    self
    &self
    &mut self
如果你需要
dyn Trait + async
,使用
#[async_trait]
或者手动返回
Box<dyn Future>
——原生trait异步方法目前还不支持对象安全。

Async Rust Rules

异步Rust规则

Runtime: Tokio is the default

运行时:默认使用Tokio

Use
tokio
with
#[tokio::main]
and
#[tokio::test]
. For CPU-bound work inside an async context, use
tokio::task::spawn_blocking
or
rayon
.
使用
tokio
配合
#[tokio::main]
#[tokio::test]
。对于异步上下文里的CPU密集型任务,使用
tokio::task::spawn_blocking
或者
rayon

Native async traits — drop
#[async_trait]
where possible

原生异步Trait——尽可能停用
#[async_trait]

Since Rust 1.75,
async fn
in traits works natively. Use native syntax unless you need
dyn Trait
with async methods.
从Rust 1.75版本开始,trait中的
async fn
已原生支持。除非你需要异步方法配合
dyn Trait
使用,否则优先使用原生语法。

The Send/Sync rule

Send/Sync规则

Futures passed to
tokio::spawn
must be
Send
. The #1 cause of non-Send futures: holding a
MutexGuard
(or any
!Send
type) across an
.await
point.
Fix: 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
传递给
tokio::spawn
的Future必须实现
Send
。非Send Future的首要原因:在
.await
点之间持有
MutexGuard
(或任何
!Send
类型)。
修复方案: 在await之前释放guard,或者将锁的作用域限制在代码块内:
rust
{
    let mut guard = lock.lock().unwrap();
    guard.push(42);
} // guard dropped
do_async_thing().await; // future is Send

Cancellation safety — the most dangerous async footgun

取消安全——最危险的异步陷阱

Any future can be dropped at any
.await
point (especially in
tokio::select!
). Know which operations are cancel-safe:
OperationCancel-safe?
mpsc::Receiver::recv
Yes
AsyncReadExt::read
Yes
AsyncWriteExt::write_all
No
AsyncBufReadExt::read_line
No
For cancel-unsafe code: wrap in
tokio::spawn
(dropping a
JoinHandle
does not cancel the spawned task) or use
tokio_util::sync::CancellationToken
for cooperative cancellation.
任何Future都可能在任意
.await
点被丢弃(尤其在
tokio::select!
中)。请明确哪些操作是取消安全的:
操作是否取消安全?
mpsc::Receiver::recv
AsyncReadExt::read
AsyncWriteExt::write_all
AsyncBufReadExt::read_line
对于非取消安全的代码:用
tokio::spawn
包裹(丢弃
JoinHandle
不会取消已经 spawn 的任务),或者使用
tokio_util::sync::CancellationToken
实现协作式取消。

Structured concurrency: use
JoinSet

结构化并发:使用
JoinSet

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??;
}

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
UserId
with
OrderId
:
rust
struct UserId(u64);
struct OrderId(u64);
// fn process(user: UserId, order: OrderId) — compiler prevents swaps
包装基础类型创建独立类型,避免把
UserId
OrderId
混用:
rust
struct UserId(u64);
struct OrderId(u64);
// fn process(user: UserId, order: OrderId) — compiler prevents swaps

Typestate — 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 变体

MarkerVarianceUse for
PhantomData<T>
Covariant"Owns" a T conceptually
PhantomData<fn(T)>
ContravariantConsumes T (rare)
PhantomData<fn(T) -> T>
InvariantMust be exact type
PhantomData<*const T>
InvariantRaw pointer semantics

标记变体类型适用场景
PhantomData<T>
协变概念上“拥有”一个T
PhantomData<fn(T)>
逆变消费T(罕见)
PhantomData<fn(T) -> T>
不变必须是精确类型
PhantomData<*const 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 vecs
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 vecs

The zero-cost rule

零开销规则

Iterator chains (
filter().map().sum()
) compile to the same code as hand-written loops — prefer them for readability. But stateful iterator chains can block auto-vectorization; see
references/performance.md
for SIMD details.

迭代器链(
filter().map().sum()
)编译后和手写循环的代码完全一致——优先使用迭代器保证可读性。但有状态的迭代器链可能会阻塞自动向量化,SIMD相关细节请参考
references/performance.md

Unsafe Policy

Unsafe代码规范

  1. Minimize scope — wrap only the minimum number of lines in
    unsafe {}
    .
  2. Mandatory
    // SAFETY:
    comment
    on every
    unsafe
    block explaining why the invariants are upheld.
  3. Prefer safe abstractions
    as
    casts,
    bytemuck::cast
    ,
    from_raw_parts
    over
    transmute
    . Use
    transmute
    only as last resort with turbofish syntax.
  4. FFI boundary rule: generate bindings with
    bindgen
    , wrap in a thin safe Rust API, document every invariant.
  5. Never use
    unsafe
    to bypass the borrow checker.
    If you think you need to, redesign the data structure.

  1. 最小化作用域——仅将最少的必要代码包裹在
    unsafe {}
    中。
  2. 每个
    unsafe
    块必须附带
    // SAFETY:
    注释
    ,说明为何代码满足不变量要求。
  3. 优先使用安全抽象——优先用
    as
    转换、
    bytemuck::cast
    from_raw_parts
    ,而非
    transmute
    。仅在万不得已时使用带turbofish语法的
    transmute
  4. FFI边界规则:
    bindgen
    生成绑定,封装为精简的安全Rust API,为每个不变量添加文档说明。
  5. 永远不要用
    unsafe
    绕过借用检查器
    。如果你认为需要这么做,请重新设计数据结构。

Common Pitfalls

常见陷阱

  1. Holding
    MutexGuard
    across
    .await
    — makes the future
    !Send
    , breaks
    tokio::spawn
    . Scope the lock in a block before awaiting.
  2. RefCell
    double borrow panic
    borrow_mut()
    panics if any borrow is live. Use
    try_borrow_mut()
    when borrow lifetimes aren't fully controlled.
  3. Mutex
    deadlock
    — Rust's
    Mutex
    is non-reentrant. Never lock the same mutex twice on one thread. Acquire multiple locks in consistent order.
  4. collect::<Vec<Result<T, E>>>()
    vs
    collect::<Result<Vec<T>, E>>()
    — the second form fails fast on first error and is almost always what you want.
  5. Accepting
    &String
    instead of
    &str
    &String
    auto-derefs to
    &str
    but not vice versa. Always accept
    &str
    in function signatures.
  6. unwrap()
    in library code
    — crashes the caller. Use
    ?
    with proper error types, or
    expect()
    with documented invariant.
  7. Forgetting
    #[must_use]
    on
    Result
    -returning functions
    — callers may silently ignore errors. The compiler warns, but custom types need the attribute.
  8. Using
    std::sync::Mutex
    in async code
    — blocks the executor thread. Use
    tokio::sync::Mutex
    for async contexts.
  9. String::from
    in hot loops
    — allocates each iteration. Pre-allocate with
    String::with_capacity()
    or use
    Cow<str>
    .
  10. Ignoring cancellation safety in
    select!
    — the non-winning future is dropped. Cancel-unsafe operations lose data silently.
  11. clone()
    as first instinct
    — usually a sign of fighting the borrow checker. Restructure ownership or use references first.
  12. Box<dyn Error>
    instead of proper error enum
    — loses the ability to match on specific variants. Use
    thiserror
    for structured errors.

  1. .await
    之间持有
    MutexGuard
    ——会导致Future为
    !Send
    ,破坏
    tokio::spawn
    的使用。请在await之前将锁的作用域限制在独立代码块内。
  2. RefCell
    双重借用panic
    ——如果存在任何活跃借用时调用
    borrow_mut()
    会触发panic。当借用生命周期不可控时使用
    try_borrow_mut()
  3. Mutex
    死锁
    ——Rust的
    Mutex
    不可重入,永远不要在同一个线程中对同一个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]
    ——调用方可能会静默忽略错误。编译器会为原生Result类型报警,但自定义类型需要显式添加该属性。
  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

Reference Files

参考文件

Read the relevant reference file when working with a specific topic:
FileWhen to read
references/ownership.md
Interior mutability, smart pointers, Cow, Pin, lifetime tricks
references/traits.md
Trait objects, sealed traits, blanket impls, HRTB, variance
references/error-handling.md
thiserror v2, anyhow, Result combinators, error design
references/async-rust.md
Tokio runtime, cancellation, JoinSet, Send/Sync, select!
references/performance.md
Zero-cost, SIMD, arena allocation, rayon, cache optimization
references/unsafe-ffi.md
Unsafe superpowers, FFI with bindgen, transmute, raw pointers
references/macros.md
Declarative macros, proc macros, derive macros, syn/quote
references/type-patterns.md
Newtype, typestate, PhantomData, const generics, builder
处理特定主题时请阅读对应的参考文件:
文件适用场景
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
新类型、类型状态、PhantomData、const泛型、构建器