rust-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRust best practices
Rust 最佳实践
Distilled from five sources, with full per-source digests in : the Google
Rust Style Guide, the official Rust API Guidelines, Effective Rust (David Drysdale),
Rust Design Patterns (rust-unofficial), and Microsoft's Pragmatic Rust Guidelines.
Where the sources disagree, this file says which to follow and why.
references/提炼自五个来源,完整的逐来源摘要见 :Google Rust Style Guide、官方 Rust API Guidelines、Effective Rust(David Drysdale)、Rust Design Patterns(rust-unofficial),以及 Microsoft's Pragmatic Rust Guidelines。若各来源存在分歧,本文件会说明应遵循哪一方及其原因。
references/1. Mindset
1. 思维模式
- Write Rust-shaped code. Don't port C++, Java, C#, or Python constructs one-to-one.
Model errors with , resources with ownership and
Result, polymorphism with traits and enums, and nullability withDrop. Inheritance-flavored tricks (Optionto a "base" struct,Deref/Managertypes,Factoryhelpers) are smells.throw_if_null - Make invalid states unrepresentable. Push invariants into types: enums instead of bool flags and "must be X if Y" comments, newtypes for units and IDs, fallible constructors that validate once. The compiler then enforces what a comment could only request.
- Application code is not library code. Library code (anything with external users)
follows the API-design rules below strictly. Application code should favor simplicity
and directness: concrete types, -style errors,
anyhowreturn values, no speculative generality.Vec - Let the tools do the work. rustfmt owns formatting, clippy owns idiom checks, the compiler owns memory safety. Spend your attention on design.
- 编写符合 Rust 范式的代码。 不要将 C++、Java、C# 或 Python 的构造方式一对一地移植过来。使用 对错误建模,使用所有权和
Result对资源建模,使用 trait 和 enum 对多态建模,并使用Drop对可空性建模。带有继承思维色彩的技巧(通过Option指向一个“基类”struct、使用Deref/Manager类型、Factory辅助函数)都是坏味道。throw_if_null - 使无效状态无法表示。 将不变量编码进类型:用 enum 代替 bool 标志和“如果是 Y 就必须是 X”之类的注释,用 newtype 表示单位和 ID,并使用可失败的构造函数一次性完成验证。让编译器强制执行原本只能依靠注释要求的约束。
- 应用代码不是库代码。 库代码(即任何存在外部用户的代码)应严格遵循下文的 API 设计规则。应用代码应优先考虑简单性和直接性:使用具体类型、风格错误、
anyhow返回值,不做推测性的泛化。Vec - 让工具完成工作。 rustfmt 负责格式化,clippy 负责惯用法检查,编译器负责内存安全。将注意力放在设计上。
2. Before you hand back code
2. 交还代码之前
Run these and fix everything they report:
bash
cargo fmt
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo doc --no-deps # libraries: catches broken doc links and missing docs- Fix a lint rather than silencing it. When a lint is genuinely wrong for one item, use
on the narrowest item.
#[expect(clippy::name, reason = "...")]warns when the lint stops firing, so stale suppressions get cleaned up. Useexpectonly in generated code or macro output.#[allow] - Never put in source. New toolchains add new warnings and will break the build. Enforce warnings-as-errors in CI instead (
#![deny(warnings)]orcargo clippy -- -D warnings).RUSTFLAGS="-D warnings" - Configure lints once in (
Cargo.toml,[lints.rust], or[lints.clippy]), not with scattered crate attributes. A good baseline:[workspace.lints],unsafe_op_in_unsafe_fn,missing_debug_implementations, and where the project tolerates themclippy::allandclippy::undocumented_unsafe_blocks.clippy::allow_attributes_without_reason
运行以下命令,并修复它们报告的所有问题:
bash
cargo fmt
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo doc --no-deps # libraries: catches broken doc links and missing docs- 应修复 lint,而不是压制 lint。当某条 lint 对特定条目确实不适用时,在范围最小的条目上使用 。
#[expect(clippy::name, reason = "...")]会在 lint 不再触发时发出警告,因此陈旧的抑制项能够被清理。仅在生成代码或宏输出中使用expect。#[allow] - 绝不要在源码中放置 。新工具链会引入新的警告,从而导致构建失败。应在 CI 中启用将警告视为错误的策略(
#![deny(warnings)]或cargo clippy -- -D warnings)。RUSTFLAGS="-D warnings" - 在 中统一配置一次 lint(
Cargo.toml、[lints.rust]或[lints.clippy]),而不是在各个 crate 中分散添加属性。推荐的基础配置包括:[workspace.lints]、unsafe_op_in_unsafe_fn、missing_debug_implementations;如果项目能够容忍,也可以加入clippy::all和clippy::undocumented_unsafe_blocks。clippy::allow_attributes_without_reason
3. Ownership and borrowing
3. 所有权与借用
Prefer unique ownership. Reach for / only when shared ownership is part of
the design, and for / only when interior mutability is. They are not
failures, but they move checking from compile time to run time, so use them on purpose.
RcArcRefCellMutexDon't clone to make the borrow checker stop complaining. A clone that exists only to
end a borrow produces a silent copy whose later changes are lost. Try these first, in
roughly this order:
- Extend a temporary with a named , or shorten a borrow with a block.
let - Split a long expression into typed intermediates so the failing step is visible.
- Move a field out of with
&mut self(needsstd::mem::take),Default, orstd::mem::replaceinstead of cloning it.Option::take - Split a large struct into smaller ones so disjoint fields can be borrowed separately. The compiler tracks disjoint field borrows inside one function, but not across method calls.
- Clone small, immutable data deliberately, and say so in a comment if it's not obvious.
- Use indices into an owning , or
VecwithRc<RefCell<_>>back-pointers, as a last resort. Avoid self-referential structs.Weak
rust
// Move out of a &mut enum without cloning.
fn a_to_b(e: &mut MyEnum) {
if let MyEnum::A { name, x: 0 } = e {
*e = MyEnum::B { name: std::mem::take(name) };
}
}Accept the most general borrowed type. not , not ,
not , not . Deref coercion then lets callers pass
owned values, borrows, or literals. Where you only read a path, string, or byte buffer,
/ / is fine on parameters (never
on struct fields; store and ). Library code that only iterates should
take ; application code may simply take .
&str&String&[T]&Vec<T>&T&Box<T>Option<&T>&Option<T>impl AsRef<Path>impl AsRef<str>impl AsRef<[u8]>PathBufStringimpl IntoIterator<Item = T>&[T]Take ownership only when you need it, and then take it by value. If a function stores
or consumes its argument, take , not plus an internal . If it only
reads, take ; if it mutates in place, . Return multiple values as a tuple or
struct, not through out-parameters (reusing a caller-owned buffer, as
does, is the exception).
T&T.clone()&T&mut T&mutRead::readPrefer owned data in structs. A struct holding forces a lifetime onto every
container. Owning a or is usually simpler and rarely the bottleneck.
When a type does carry a lifetime, give it a descriptive name (, ); and
are fine on simple functions.
&'a TStringVec<u8>'src'conn'_'aFreeze after setup. Build with , then rebind so later
code cannot mutate it by accident.
let mutlet data = data;Keep , , , and out of public signatures. Take ,
, or and let callers decide how to own things. A service that must be shared
implements a cheap by wrapping its state in internally.
RcArcBoxRefCell&T&mut TTCloneArc<Inner>优先采用唯一所有权。 只有当共享所有权本身就是设计的一部分时,才使用 /;只有当需要内部可变性时,才使用 /。它们并非错误,但会将检查从编译期转移到运行期,因此必须有意识地使用。
RcArcRefCellMutex不要为了让 borrow checker 停止报错而调用 。 仅为结束借用而存在的 clone 会产生静默副本,导致后续修改丢失。尝试采用以下方法,顺序大致如下:
clone- 将临时值绑定到具名 ,以延长其生命周期,或使用代码块缩短借用范围。
let - 将长表达式拆分为具有明确类型的中间值,使失败步骤清晰可见。
- 使用 (要求类型实现
std::mem::take)、Default或std::mem::replace从Option::take中移出字段,而不是克隆字段。&mut self - 将大型 struct 拆分为更小的 struct,使互不相交的字段可以分别借用。编译器会在单个函数内跟踪不相交字段的借用,但不会跨方法调用进行跟踪。
- 有意识地克隆体积较小且不可变的数据;如果并非显而易见,应在注释中说明。
- 最后才考虑使用指向拥有型 的索引,或使用带
Vec反向指针的Weak。避免使用自引用 struct。Rc<RefCell<_>>
rust
// Move out of a &mut enum without cloning.
fn a_to_b(e: &mut MyEnum) {
if let MyEnum::A { name, x: 0 } = e {
*e = MyEnum::B { name: std::mem::take(name) };
}
}接受最通用的借用类型。 使用 而非 ,使用 而非 ,使用 而非 ,使用 而非 。随后可通过 Deref coercion 让调用者传入拥有型值、借用或字面量。如果只是读取路径、字符串或字节缓冲区,参数可以使用 、 或 ,但绝不应将其用于 struct 字段;字段应存储 和 。只负责迭代的库代码应接收 ;应用代码则可以直接接收 。
&str&String&[T]&Vec<T>&T&Box<T>Option<&T>&Option<T>impl AsRef<Path>impl AsRef<str>impl AsRef<[u8]>PathBufStringimpl IntoIterator<Item = T>&[T]仅在需要时取得所有权,并按值取得。 如果函数会存储或消费参数,应接收 ,而不是接收 后在内部调用 。如果函数只读取参数,应接收 ;如果函数会原地修改参数,应接收 。多个返回值应使用 tuple 或 struct 表达,而不是通过 输出参数返回(复用调用者拥有的缓冲区是例外,例如 )。
T&T.clone()&T&mut T&mutRead::readstruct 中优先保存拥有型数据。 持有 的 struct 会迫使每个容器都携带生命周期。拥有 或 通常更简单,而且很少会成为性能瓶颈。当类型确实需要携带生命周期时,应为其使用描述性名称(、);简单函数中使用 和 即可。
&'a TStringVec<u8>'src'conn'_'a完成初始化后将其冻结。 使用 构建数据,然后重新绑定为 ,使后续代码无法意外修改它。
let mutlet data = data;不要让 、、 和 出现在公共签名中。 应接收 、 或 ,由调用者决定如何拥有数据。如果服务必须共享,可通过在内部包装 来实现低成本的 。
RcArcBoxRefCell&T&mut TTArc<Inner>Clone4. Types and API design
4. 类型与 API 设计
Naming
命名
| Item | Case | Notes |
|---|---|---|
| Types, traits, enum variants | | Acronyms are one word: |
| Functions, methods, modules, locals | | |
| Constants, statics | | |
| Type parameters | short, from the main bound | |
| Lifetimes | short lowercase | descriptive when on a type |
- No prefix on getters:
get_,fn len(&self),fn name(&self) -> &str. Use barefn name_mut(&mut self)only when there is one obvious thing to get (get). Related:Cell::get,set_foo(T),take_foo()for closure access, and unsafewith_foo(|f| ..)siblings for checked accessors._unchecked - Conversion prefixes signal cost and ownership: (free, borrow to borrow),
as_(expensive, borrow to owned or borrow to borrow),to_(consumesinto_). Putselfin type order:mut, notas_mut_slice.as_slice_mut - Constructors: (primary),
new/with_capacity(variants),new_with_foo(fallible),try_new(conversion; pair important ones with afrom_fooimpl). I/O resources use domain verbs:From,File::open.TcpStream::connect - /
iter/iter_mutreturn types namedinto_iter/Iter/IterMut;IntoIterreturnskeys().Keys - Error types read verb-object-error: , not
ParseBoolError.BoolParseError - Avoid weasel suffixes ,
Service,Manager,Factory,Helper. Name the thing it is (Util,Bookings,Dispatcher). Don't repeat the module name in the type (FooBuilder, notfoo::Id). Avoid abbreviations an outsider wouldn't know.foo::FooId - If you implement , implement
len().is_empty() - Feature names are bare: ,
std,serde, neverallocoruse-std.no-std
| 项目 | 大小写 | 说明 |
|---|---|---|
| 类型、trait、enum 变体 | | 缩写视为一个单词: |
| 函数、方法、模块、局部变量 | | |
| 常量、静态变量 | | |
| 类型参数 | 简短,并源自主要约束 | |
| 生命周期 | 短小写字母 | 用于类型时应具有描述性 |
- getter 不要使用 前缀:使用
get_、fn len(&self)、fn name(&self) -> &str。只有在要获取的对象唯一且明确时才使用裸fn name_mut(&mut self)(例如get)。相关形式包括:用于获取值的Cell::get、set_foo(T),用于 closure 访问的take_foo(),以及 checked accessor 对应的 unsafewith_foo(|f| ..)变体。_unchecked - 转换前缀应表达成本和所有权:表示无成本、从借用到借用,
as_表示成本较高、借用到拥有型或借用到借用,to_表示消费into_。self应放在类型顺序的正确位置:使用mut,而不是as_mut_slice。as_slice_mut - 构造函数:(主要构造函数)、
new/with_capacity(变体)、new_with_foo(可失败构造函数)、try_new(转换;重要的转换应配套提供from_foo实现)。I/O 资源应使用领域动词,例如From、File::open。TcpStream::connect - /
iter/iter_mut的返回类型应命名为into_iter/Iter/IterMut;IntoIter应返回keys()。Keys - 错误类型应采用“动词-对象-错误”的命名方式:使用 ,而不是
ParseBoolError。BoolParseError - 避免使用含糊且无实际信息量的后缀,如 、
Service、Manager、Factory、Helper。应按实体本身命名,例如Util、Bookings、Dispatcher。不要在类型名中重复模块名(使用FooBuilder,而不是foo::Id)。避免使用外部人员无法理解的缩写。foo::FooId - 如果实现了 ,也应实现
len()。is_empty() - Feature 名称应保持简洁:、
std、serde,不要使用alloc或use-std。no-std
Model the domain with types
使用类型建模领域概念
- Newtypes for semantic distinctions: and
struct Miles(f64)cannot be mixed, where a type alias can. Addstruct Kilometers(f64)when layout matters, and derive or forward the traits you need (#[repr(transparent)]cuts the boilerplate). A newtype that encodes an invariant (derive_more) validates in a fallible constructor (Month,TryFrom,FromStr) and never offers an infallibletry_newfrom the raw type.From - No parameters in public or shared signatures.
boolis unreadable at the call site and cannot grow a third mode. Use a two-variant enum (report(true),Verbosity::Summary) or two methods (Verbosity::Full,report()):report_verbose(), notWidget::new(Size::Small, Shape::Round). The only exception is a private helper whose call sites sit a few lines away. When refactoring, an existingWidget::new(true, false)parameter is one of the smells to remove, not a detail to preserve.bool - Named fields over tuples, except single-field newtypes and very local code.
- for absence, never sentinels such as
Optionor-1. Data-carrying enums instead of "field X is only meaningful when Y is set"."" - Use the strongest std type: /
PathBuffor paths,&Pathfor spans,Durationinternally only (keep plain integers at API boundaries).NonZeroU32 - Independent flags use the crate; an enum models exactly one choice.
bitflags
- 使用 newtype 表达语义差异:和
struct Miles(f64)不能混用,而类型别名可以。内存布局很重要时,添加struct Kilometers(f64),并派生或转发所需的 trait(#[repr(transparent)]可以减少样板代码)。如果 newtype 编码了不变量(derive_more),应通过可失败构造函数(Month、TryFrom、FromStr)进行验证,并且绝不提供从原始类型进行无损try_new转换的 infallible 实现。From - 公共或共享签名中不要使用 参数。
bool在调用处难以理解,也无法扩展第三种模式。应使用包含两个变体的 enum(report(true)、Verbosity::Summary),或提供两个方法(Verbosity::Full、report()):使用report_verbose(),而不是Widget::new(Size::Small, Shape::Round)。唯一例外是调用处仅相隔几行的私有辅助函数。重构时,现有Widget::new(true, false)参数也是应消除的坏味道,而不是必须保留的细节。bool - 使用具名字段,而不是 tuple,单字段 newtype 和高度局部的代码除外。
- 使用 表示缺失,绝不要使用
Option或-1等哨兵值。 使用携带数据的 enum,而不是用“字段 X 仅在字段 Y 被设置时才有意义”的方式建模。"" - 使用最合适的标准类型: 路径使用 /
PathBuf,时间跨度使用&Path,Duration仅在内部使用(API 边界仍保留普通整数)。NonZeroU32 - 相互独立的标志应使用 crate;enum 用于表达恰好只有一种选择的情况。
bitflags
Derive and implement the standard traits eagerly
尽早派生并实现标准 trait
Downstream crates cannot add std traits to your types (orphan rule), so you must. Derive
whenever every field supports it: (on every public type; hand-write it to redact
secrets), (except unique resources and key material), (only small
bitwise-copyable types, because the compiler copies silently), / ,
/ (never ship without when a total order exists),
(consistent with ), . Implement for anything meant to be
read: error types, string-like wrappers. Add to pure functions and to
types, such as errors and lazy values, whose discarding is a bug.
DebugCloneCopyPartialEqEqPartialOrdOrdPartialOrdOrdHashEqDefaultDisplay#[must_use]Implement / , never / directly (blanket impls give you
those). Prefer / over , which silently allows lossy casts. Implement
rather than an ad-hoc method, and on
collections, and for both owned and borrowed forms. Implement only
on smart pointers and owning collections ( to ), never to fake inheritance.
Overload operators only for their mathematical meaning.
FromTryFromIntoTryIntofromintoasFromStrfrom_strFromIteratorExtendIntoIteratorDerefVec<T>[T]Don't put derivable trait bounds on struct definitions: ,
not . Bounds belong on the blocks that need them.
#[derive(Clone)] struct Good<T>struct Bad<T: Clone + Debug>implIf you hand-write , hand-write , , and to agree with it
(compare the same fields, ignore the same fields). A derived next to a custom
breaks the std contract and misbehaves in and /. Say in the
docs which fields equality ignores.
OrdPartialEqPartialOrdHashPartialEqOrdBTreeMapsortdedup下游 crate 无法为自己的类型添加 std trait(orphan rule),因此你必须完成这些工作。只要每个字段都支持,就应进行派生:公共类型均应派生 (可手写实现以隐藏秘密信息),(唯一资源和密钥材料除外),(仅适用于体积小且可按位复制的类型,因为编译器会静默复制),/,/(存在全序时,绝不要在缺少 的情况下提供 ),与 保持一致的 ,以及 。为所有用于展示的内容实现 ,例如错误类型和类字符串包装器。对于纯函数,以及丢弃后即构成错误的对象(例如错误和惰性值),应添加 。
DebugCloneCopyPartialEqEqPartialOrdOrdOrdPartialOrdEqHashDefaultDisplay#[must_use]实现 /,不要直接实现 /,因为 blanket impl 会自动提供后两者。优先使用 /,而不是可能静默产生有损转换的 。实现 ,而不是临时定义一个 方法;为集合实现 和 ;为拥有型和借用型形式都实现 。仅在智能指针和拥有型集合( 到 )上实现 ,绝不要借此模拟继承。仅在操作符具有数学含义时才重载操作符。
FromTryFromIntoTryIntofromintoasFromStrfrom_strFromIteratorExtendIntoIteratorVec<T>[T]Deref不要在 struct 定义上添加可通过派生获得的 trait bound:使用 ,而不是 。trait bound 应仅放在需要它们的 块上。
#[derive(Clone)] struct Good<T>struct Bad<T: Clone + Debug>impl如果手写 ,则应同时手写与其保持一致的 、 和 (比较相同的字段、忽略相同的字段)。在自定义 旁边使用派生的 会破坏 std 契约,并导致 以及 / 出现异常行为。应在文档中说明相等性比较会忽略哪些字段。
OrdPartialEqPartialOrdHashOrdPartialEqBTreeMapsortdedupConstructors, builders, defaults
构造函数、Builder 与默认值
- Provide even when
new()exists, and make them agree (one delegates to the other). Derive or implementDefaultso callers can writeDefault; bag-of-options structs with public fields should always have it...Default::default() - Up to two optional parameters: inherent constructors (,
new,with_a). Beyond that, or when construction validates or has side effects, add a builder:with_breturnsFoo::builder(), setters are named after fields (FooBuilder, nottimeout(d)) and infallible, andset_timeoutdoes all validation including cross-field checks. Prefer non-consuming setters (build() -> Result<Foo, Error>) unless&mut self -> &mut Selfmust move a value out.build - A constructor with four or more loose arguments should group them into semantic types:
rather than four
Deposit::new(account, amount)/&strparameters.u64
- 即使已经存在 ,也应提供
Default,并确保二者行为一致(其中一个委托给另一个)。应派生或实现new(),使调用者可以编写Default;具有公共字段、且包含多个可选配置的 struct 应始终支持..Default::default()。Default - 最多支持两个可选参数时,使用固有构造函数(、
new、with_a)。超过两个参数,或者构造过程需要验证或产生副作用时,应添加 builder:with_b返回Foo::builder();setter 以字段命名(使用FooBuilder,而不是timeout(d))且 infallible;set_timeout负责包括跨字段检查在内的全部验证。除非build() -> Result<Foo, Error>必须移出某个值,否则应优先使用不消费自身的 setter(build)。&mut self -> &mut Self - 包含四个或更多独立参数的构造函数,应将这些参数组织为具有语义的类型:使用 ,而不是传入四个
Deposit::new(account, amount)/&str参数。u64
Visibility and evolution
可见性与演进
-
Private by default. Fields stay private unless the struct is passive C-style data. Widen toor
pub(crate)beforepub(super). Making an item more public later is non-breaking; the reverse is breaking, and everypubitem is a promise.pub -
Mark public enums and public-field structs that cross crate boundariesunless the set is a stable ontology. When matching one, list the variants you handle and add a wildcard arm.
#[non_exhaustive] -
Seal traits you don't want implemented downstream, and say so in the docs:rust
pub trait TheTrait: private::Sealed { /* ... */ } mod private { pub trait Sealed {} impl Sealed for usize {} } -
Hide implementation iterator types behind(internal APIs) or a newtype (public APIs you must keep stable).
impl Iterator<Item = T> -
Expose each public item at exactly one path. Don'ta foreign crate's items unless you are an umbrella crate. Avoid leaking third-party types into your public API; feature-gate
pub useimpls behind a feature namedserde. If a dependency's type must appear, re-export it so callers can name the matching version.serde -
Mark a functiononly if it will stay
const. Don't exposeconstenums or other internals that lock in failure modes you consider private.ErrorKind
-
默认私有。除非 struct 是被动的 C 风格数据,否则字段应保持私有。在扩大可见性时,先使用或
pub(crate),再使用pub(super)。将项目改为公开通常不会破坏兼容性,而反向修改则属于破坏性变更;每个pub项目都是对使用者的承诺。pub -
对于跨越 crate 边界的公共 enum 和具有公共字段的 struct,除非其变体集合属于稳定的领域概念,否则应标记。匹配此类 enum 时,应列出明确处理的变体,并添加通配分支。
#[non_exhaustive] -
对于不希望下游实现的 trait,应将其封闭,并在文档中说明:rust
pub trait TheTrait: private::Sealed { /* ... */ } mod private { pub trait Sealed {} impl Sealed for usize {} } -
内部 API 应通过隐藏实现迭代器类型;必须保持稳定的公共 API 应通过 newtype 隐藏。
impl Iterator<Item = T> -
每个公共项目应仅通过一个路径公开。除非当前 crate 是聚合 crate,否则不要外部 crate 的项目。避免将第三方类型泄漏到公共 API 中;应将
pub use实现置于名为serde的 feature 之后。如果依赖项的类型必须出现在 API 中,应重新导出该类型,使调用者能够引用匹配的版本。serde -
仅当函数能够持续保持为时,才将其标记为
const。不要公开constenum 或其他会将不希望成为公共契约的失败模式固化的内部实现。ErrorKind
Traits and generics
Trait 与泛型
- Define a trait only for generic programming (,
T: Trait,impl Trait) or as an extension trait (dyn Trait). Don't use traits merely to organize code. Put essential functionality in inherent methods and have trait impls forward to them, so callers need no import.FooExt - Prefer, in order: concrete types; generics (,
T: Trait);impl Trait, ideally wrapped in your own struct (dyn Trait). Generics give static dispatch and inlining; trait objects give heterogeneous collections and smaller binaries. Don't nest generics on service-level types (struct DynStore(Arc<dyn Store>)is fine;Service<Backend>is not).Service<Backend<Store>> - Decide early whether a trait must be object-safe. Generic methods or outside the receiver break it; exclude such methods with
Self.where Self: Sized - Keep required trait methods minimal and provide defaults for everything derivable from
them, as does from
Iterator.next - If a function has a clear receiver, make it a method. A computation with no receiver is
a free function, not . Associated functions are for construction.
Database::check(params) - Closures: use the most general bound that works (if called once,
FnOnceif it mutates, elseFnMut) and preferFnbounds to bareFn*pointers. Closure parameters go last; don't take more than one.fn - Keep parameter order consistent across a crate: call-specific first, ubiquitous context (loggers, allocators) last.
- Reach for a macro only when functions, generics, and traits cannot do the job. Prefer
to proc macros, make macro input look like the output it generates, and never let a macro change a type's kind or a function's signature.
macro_rules!
- 仅在泛型编程(、
T: Trait、impl Trait)或扩展 trait(dyn Trait)场景中定义 trait。不要仅仅为了组织代码而使用 trait。应通过固有方法提供核心功能,并让 trait 实现转调这些方法,从而避免调用者额外导入 trait。FooExt - 选择顺序依次为:具体类型;泛型(、
T: Trait);impl Trait,最好包装在自己的 struct 中(dyn Trait)。泛型提供静态分派和内联;trait object 支持异构集合并可生成更小的二进制文件。不要在服务级类型上嵌套泛型(struct DynStore(Arc<dyn Store>)可以,Service<Backend>不可以)。Service<Backend<Store>> - 应尽早决定 trait 是否必须支持 object safety。泛型方法或接收者之外的 会破坏 object safety;可使用
Self排除这些方法。where Self: Sized - 必需的 trait 方法应保持最少,并为所有可以推导的行为提供默认实现,就像 从
Iterator获得默认实现一样。next - 如果函数具有明确的接收者,应将其定义为方法。没有接收者的计算应使用自由函数,而不是 。关联函数用于构造。
Database::check(params) - Closure:使用能够满足需求的最通用约束(只调用一次时使用 ,会修改捕获状态时使用
FnOnce,否则使用FnMut),并优先使用Fn约束,而不是裸Fn*指针。Closure 参数应放在最后,且不要接受多个 closure 参数。fn - 在整个 crate 中保持参数顺序一致:与调用场景密切相关的参数放在前面,通用上下文(logger、allocator)放在最后。
- 仅当函数、泛型和 trait 都无法完成任务时才使用宏。优先选择 而非过程宏;宏输入应看起来像其生成的输出,并且绝不能改变类型的种类或函数的签名。
macro_rules!
Refactoring someone else's code
重构他人编写的代码
- Fix every smell the request is about, not just the cheap ones: clones that only
satisfy the borrow checker, /
&Stringparameters,&Vec<T>on fallible paths,unwrapin signatures,Rc<RefCell<_>>getters,get_parameters,boolstring concatenation,+, error types without#![deny(warnings)], nondeterministicErroriteration in output.HashMap - Keep public behavior identical unless changing it is the point: the same operations,
the same success and failure semantics, the same output bytes. Turning a panic into an
is the change callers want. Dropping an error variant, removing a derive, or changing what a constructor takes is not; if a signature must change, show the before and after in the change list and say why.
Err - Preserve quirks you don't understand and flag them, rather than silently "fixing" them.
- When the module has no tests, pin the current behavior with a few before you touch it.
- 修复请求涉及的所有坏味道,而不只是容易处理的问题:仅为满足 borrow checker 而存在的 clone、/
&String参数、可失败路径中的&Vec<T>、签名中的unwrap、带Rc<RefCell<_>>的 getter、get_参数、字符串bool拼接、+、未实现#![deny(warnings)]的错误类型,以及输出中不确定的Error迭代顺序。HashMap - 除非行为变更本身是任务目标,否则应保持公共行为完全一致:相同的操作、相同的成功与失败语义,以及相同的输出字节。将 panic 转换为 往往是调用者期望的变更。删除错误变体、移除派生或更改构造函数参数则不属于此类变更;如果必须修改签名,应在变更列表中展示修改前后内容并说明原因。
Err - 保留那些尚未理解但具有重要影响的特殊行为,并将其标记出来,而不是静默地“修复”。如果模块尚无测试,应在修改前通过一些测试锁定当前行为。
5. Errors and panics
5. 错误与 Panic
Pick the mechanism by contract:
| Situation | Use |
|---|---|
Absence is normal ( | |
| The caller may need to know what went wrong | |
| A bug or violated invariant inside your code | |
Panics are not exceptions. A panic means "stop now". Never use one to report bad
input, never rely on for control flow (it does nothing under
and can leave state inconsistent), and never catch a panic and continue
inside a library. Where a panicking convenience is justified, pair it with a fallible API
( / , / ) and document it under .
catch_unwindpanic = "abort"getget_uncheckednewtry_new# PanicsEvery intentional panic carries a message with the relevant values:
,
. Prefer to
. Don't restate the condition as the message.
assert!(buf.len() >= HEADER, "buffer too small: {} bytes", buf.len())expect("config is loaded before workers start")expect("reason")unwrap()Pattern-match instead of check-then-unwrap: or
, never .
if let Some(v) = opt { .. }let Some(v) = opt else { return .. };if opt.is_some() { opt.unwrap() }Lock poisoning is fatal: . Don't recover with
.
.lock().expect("lock poisoned")into_innerLibrary error types (any in a public ):
EResult- Implement (which requires
std::error::Error+Debug), beDisplay, and implementSend + Sync + 'staticfor wrapped causes. Never usesource(),(), orStringas a library's public error type.Box<dyn Error> - messages are lowercase, concise, and have no trailing punctuation:
Display."invalid header length" - Implement so
From<Upstream>converts. Use?only for foreign errors or to add context.map_err - Two sound shapes: an enum with one variant per cause (generates this well), or a struct per situation with a private
thiserrorpluskindand accessor methods. Either way, don't make one crate-wide enum that lists every failure mode in the crate, and don't expose details callers cannot act on.is_xxx() - If a fallible function consumes an argument, return it inside the error so the caller
can retry without cloning ().
FromUtf8Error::into_bytes - Never expose in a library's public API.
anyhow::Error
Application error handling: or with is
the right tool. Use one app-level error type, not several. may return
.
anyhoweyre.context("opening config")mainResult<(), anyhow::Error>Destructors never fail, panic, or block. Provide for
fallible teardown and let do best-effort cleanup.
close(self) -> Result<()>DropDocument failure modes: for any , for any reachable
panic, for every .
# ErrorsResult# Panics# Safetyunsafe fn根据契约选择错误处理机制:
| 情况 | 使用方式 |
|---|---|
缺失属于正常情况( | |
| 调用者可能需要了解出错原因 | |
| 代码中存在 bug 或内部不变量被破坏 | |
Panic 不是异常。 Panic 意味着“立即停止”。绝不能用 panic 报告错误输入;不要依赖 控制流程(在 下它不起作用,并且可能使状态不一致);在库中捕获 panic 后继续执行也绝不可取。如果确实需要提供会 panic 的便捷 API,应同时提供可失败 API(/、/),并在 下记录行为。
catch_unwindpanic = "abort"getget_uncheckednewtry_new# Panics每次有意触发 panic 时,消息中都应包含相关值: 例如 、。优先使用 ,而不是 。不要只是重复触发条件作为消息。
assert!(buf.len() >= HEADER, "buffer too small: {} bytes", buf.len())expect("config is loaded before workers start")expect("reason")unwrap()使用模式匹配,而不是先检查再 unwrap: 使用 或 ,不要使用 。
if let Some(v) = opt { .. }let Some(v) = opt else { return .. };if opt.is_some() { opt.unwrap() }锁中毒是致命错误: 使用 。不要通过 尝试恢复。
.lock().expect("lock poisoned")into_inner库错误类型(公共 中的任何 ):
ResultE- 实现 (这要求同时实现
std::error::Error和Debug),确保类型满足Display,并为被包装的底层原因实现Send + Sync + 'static。绝不要将source()、()或String作为库的公共错误类型。Box<dyn Error> - 消息应使用小写字母、简洁明了,并且末尾不要加标点,例如
Display。"invalid header length" - 实现 ,使
From<Upstream>能够完成转换。仅在处理外部错误或需要添加上下文时使用?。map_err - 有两种健全的形态:每种底层原因对应一个变体的 enum(可以很好地生成这种代码),或每种情况对应一个 struct,并包含私有
thiserror、kind及访问器方法。无论采用哪种形式,都不要创建一个列举整个 crate 所有失败模式的全局 enum,也不要公开调用者无法据以采取行动的细节。is_xxx() - 如果可失败函数会消费参数,应在错误中返回该参数,使调用者无需克隆即可重试()。
FromUtf8Error::into_bytes - 绝不要在库的公共 API 中公开 。
anyhow::Error
应用错误处理: 或 配合 是合适的选择。应使用一种应用级错误类型,而不是多种。 可以返回 。
anyhoweyre.context("opening config")mainResult<(), anyhow::Error>析构函数绝不失败、panic 或阻塞。 对于可能失败的清理操作,提供 ,并让 负责尽力完成的清理。
close(self) -> Result<()>Drop记录失败模式: 所有 都应提供 文档,所有可能触发的 panic 都应提供 文档,每个 都应提供 文档。
Result# Errors# Panicsunsafe fn# Safety6. Everyday idioms
6. 日常惯用法
-
Iterators over hand loops when the pipeline is short and reads top-down:,
values.iter().filter(..).map(..).collect(),.any(),.sum(). Collect fallible steps into.fold()and applyResult<Vec<_>, E>. Keep an explicit?loop when the body is large, does several jobs, or needs awkward early returns. Bind a long chain to a named variable rather than putting it in aforheader, and preferfortofor x in it..for_each(|x| ..) -
/
Optioncombinators (Result,map,and_then,ok_or_else,unwrap_or_else,as_ref) when they're clearer thanas_deref, andmatchwhen they're not.match -
/
format!with inline args (write!) for concatenation and substitution, not chainedformat!("{name}: {count}")or+.str::replaceinto an existing buffer in hot paths.write! -
Explicit types where inference obscures intent: on,
.into(),.parse(), and complex iterator results. Prefer.collect()to a turbofish.let x: Vec<u32> = .. -
Drop guards for cleanup that must run on early return or unwind. Bind them to a name (), because
let _guard = ..drops immediately.let _ = .. -
Closures that capture mixed ownership: rebind in a block, then:
moverustlet job = { let cfg = cfg.clone(); move || run(cfg, data) }; -
Import style: oneper module path (
use); types and traits imported and used unqualified (use foo::bar::{Bar, Baz};); free functions qualified by their module (Regex,mem::take). Import a trait inside the function that needs it unless the whole file uses it. No glob imports exceptfs::readin test modules anduse super::*inside a function that matches on it.use Enum::* -
Magic numbers become nameds with a doc comment saying where the value comes from and what changing it affects.
const -
Logging: structured events throughor
tracing, notlog/println!, except for a CLI's own stdout output.dbg!
-
当处理流水线较短且自上而下易于阅读时,优先使用迭代器而不是手写循环:、
values.iter().filter(..).map(..).collect()、.any()、.sum()。对于可能失败的步骤,将结果收集到.fold()并使用Result<Vec<_>, E>。如果循环体较大、承担多项任务,或需要不自然的提前返回,则保留显式?循环。应将长链绑定到具名变量,而不是放入for的头部;优先使用for,而不是for x in it。.for_each(|x| ..) -
当/
Option组合器(Result、map、and_then、ok_or_else、unwrap_or_else、as_ref)比as_deref更清晰时使用它们,否则使用match。match -
使用带内联参数的/
format!进行拼接和替换,而不是链式使用write!或+。在热路径中,使用str::replace写入已有缓冲区。write! -
在类型推断会模糊意图的位置显式指定类型: 例如、
.into()、.parse()以及复杂的迭代器结果。优先使用.collect(),而不是 turbofish。let x: Vec<u32> = .. -
使用 Drop guard 执行必须在提前返回或 unwind 期间运行的清理。应将其绑定到名称(),因为
let _guard = ..会立即将其丢弃。let _ = .. -
处理混合所有权的 closure: 在代码块中重新绑定变量,然后使用:
moverustlet job = { let cfg = cfg.clone(); move || run(cfg, data) }; -
导入风格: 每个模块路径使用一条(
use);类型和 trait 以非限定名称导入并使用(use foo::bar::{Bar, Baz};);自由函数通过其模块限定(Regex、mem::take)。除非整个文件都需要某个 trait,否则应在需要它的函数内部导入。除测试模块中的fs::read和函数内部匹配 enum 时的use super::*外,不要使用 glob 导入。use Enum::* -
魔法数字应替换为具名,并添加文档注释说明数值来源以及修改它会产生什么影响。
const -
日志: 通过或
tracing记录结构化事件,不要使用log/println!,但 CLI 自身的标准输出除外。dbg!
7. Concurrency and async
7. 并发与 Async
-
Public types, and the futures they return, should be(and usually
Send). Pin it with a compile-time check so a refactor can't silently regress it:Syncrustconst _: () = { const fn assert_send<T: Send>() {} assert_send::<MyClient>(); };Holding anor aRcborrow across anRefCellmakes a future.await.!Send -
Prefer message passing (channels) to shared state. When sharing is necessary: keep data with linked invariants under one lock, keep lock scopes to a few lines, never call user closures while holding a lock, and never return afrom a public method.
MutexGuard -
Writerather than
async fn, except in traits or when setup must run outside the async block for a hot path.fn -> impl Future -
Long CPU-bound work inside async must yield (every tens of microseconds) or move to
tokio::task::yield_now().await.spawn_blocking -
Library code doesn't spawn raw threads or assume a runtime; take an executor, a spawn function, or/
impl AsyncReadfrom the caller.impl Read
公共类型及其返回的 future 应满足 (通常也应满足 )。可通过编译期检查将其固定下来,防止重构后在不知情的情况下退化:
SendSyncrust
const _: () = {
const fn assert_send<T: Send>() {}
assert_send::<MyClient>();
};跨 持有 或 借用会使 future 变成 。
.awaitRcRefCell!Send优先使用消息传递(channel),而不是共享状态。必须共享时:将具有关联不变量的数据放在同一把锁下,将锁的作用域限制在几行以内,绝不在持锁时调用用户提供的 closure,并且绝不从公共方法返回 。
MutexGuard编写 ,而不是 ;trait 中的方法,或热路径中必须在 async block 外完成初始化的场景除外。
async fnfn -> impl Futureasync 内部的长时间 CPU 密集型工作必须让出执行权(每几十微秒调用一次 ),或者移至 。
tokio::task::yield_now().awaitspawn_blocking库代码不应直接创建裸线程,也不应假定存在某个 runtime;应从调用者处接收 executor、spawn 函数,或 /。
impl AsyncReadimpl Read8. Unsafe and FFI
8. Unsafe 与 FFI
- Write only for one of three reasons: a novel abstraction safe code can't express, a benchmarked performance need, or FFI and platform calls. Reach for std and well-reviewed crates first; they already contain the audited
unsafe.unsafe - Every block or impl gets a
unsafecomment stating why the invariants hold. Every// SAFETY:gets aunsafe fndoc section listing what the caller must uphold. Enable# Safety.unsafe_op_in_unsafe_fn - Keep in the smallest module that can uphold its invariant and wrap it in a safe API; the module boundary is the soundness boundary. A safe function that can cause undefined behavior through any sequence of safe calls is unsound, and unsound code is never acceptable.
unsafe - Mark a function only when misuse can cause undefined behavior, not because it is dangerous (
unsafeis a safe function).delete_database() - No to cast enums or dodge lifetimes, and no
transmuteto silence the compiler.unsafe impl Send - Test unsafe code under Miri and with adversarial inputs.
- FFI: on shared structs, sized integer types,
#[repr(C)]/CStringfor strings, allocate and free on the same side, wrap C-owned pointers in an RAII type whoseCStrfrees them, check for null and returnDrop, and stop panics at the boundary withResult. Generate bindings withcatch_unwindin abindgencrate and keep business logic in a safe core crate.foo-sys
- 仅应出于以下三种原因之一编写 :安全代码无法表达某种新型抽象、存在经过基准测试的性能需求,或需要处理 FFI 与平台调用。优先使用 std 和经过充分审查的 crate;它们已经包含经过审计的
unsafe实现。unsafe - 每个 block 或 impl 都应带有
unsafe注释,说明其不变量为何成立。每个// SAFETY:都应提供unsafe fn文档章节,列出调用者必须满足的条件。启用# Safety。unsafe_op_in_unsafe_fn - 将 限制在能够保证其不变量的最小模块中,并使用安全 API 对外包装;模块边界就是健全性边界。如果一个安全函数可能通过任意安全调用序列导致未定义行为,那么该函数就是不健全的,而不健全代码绝不可接受。
unsafe - 仅当误用可能导致未定义行为时,才将函数标记为 ,不要仅仅因为函数具有危险性;例如,
unsafe仍应是安全函数。delete_database() - 不要使用 转换 enum 或规避生命周期,也不要使用
transmute来压制编译器。unsafe impl Send - 应在 Miri 下并使用对抗性输入测试 unsafe 代码。
- FFI:共享 struct 应使用 和定长整数类型;字符串应使用
#[repr(C)]/CString;内存应在同一侧分配和释放;应使用 RAII 类型包装 C 所有的指针,并由CStr负责释放;应检查空指针并返回Drop;应在边界处使用Result阻止 panic 传播。使用catch_unwind在bindgencrate 中生成绑定,并将业务逻辑保留在安全的核心 crate 中。foo-sys
9. Crates, modules, dependencies
9. Crate、模块与依赖
- Prefer small, single-purpose crates in a flat workspace; never nest a crate inside another crate's
crates/. Split when a crate grows a second purpose or a subdirectory. Share versions and lints throughsrc/and[workspace.dependencies].[workspace.lints] - Modules: name a leaf module after its primary item (lives in
Foo). Non-leaf modules contain onlyfoo.rsdeclarations and re-exports. Put the essential items at the crate root and group the rest by use case (mod,account), not by kind (network,traits). Don't define a prelude.errors - Re-export individually (), never with a glob.
pub use foo::{A, B}; - Dependencies: every one costs compile time, binary size, and supply-chain exposure,
including and proc macros. Use ranges (
build.rs,"1"), never"1.4"or a needless"*". Commit"="for binaries. Audit withCargo.lock,cargo tree --duplicates,cargo deny, andcargo udeps.cargo audit - Features are additive and bare-named: ,
std,serde,alloc. Nevertest-util,no-std, or a feature that removes or changes a public item. Don't feature-gate struct fields or trait methods. Put mocks and fakes behinduse-serde.test-util - Semver: breaking changes include removing or renaming public items, adding a
variant to an enum that isn't , adding a field to a publicly constructible struct, adding a trait bound, or making a trait non-object-safe. Deprecate in a minor release before removing in a major one. Use the latest stable edition for new crates and set an MSRV deliberately.
#[non_exhaustive] - Don't run code before (no
main()-style crates). Don't use unstable features on a stable toolchain.ctor
- 优先使用小型、单一用途的 crate,并采用扁平的 workspace;绝不要在另一个 crate 的
crates/中嵌套 crate。当 crate 出现第二项用途或子目录时,应进行拆分。通过src/和[workspace.dependencies]共享版本与 lint 配置。[workspace.lints] - 模块: 叶模块以其主要项目命名(位于
Foo)。非叶模块只应包含foo.rs声明和重新导出。将核心项目放在 crate 根目录,其余项目按使用场景(mod、account)而非类别(network、traits)分组。不要定义 prelude。errors - 逐项重新导出(),绝不要使用 glob。
pub use foo::{A, B}; - 依赖: 每个依赖都会增加编译时间、二进制体积和供应链暴露风险,和过程宏也不例外。版本范围应使用
build.rs、"1",绝不使用"1.4",也不要使用不必要的"*"。二进制项目应提交"="。使用Cargo.lock、cargo tree --duplicates、cargo deny和cargo udeps进行审计。cargo audit - Feature 应具有可加性,并采用简洁名称: 、
std、serde、alloc。绝不要使用test-util、no-std,也不要使用会移除或改变公共项目的 feature。不要通过 feature 控制 struct 字段或 trait 方法。Mock 和 fake 应置于use-serde之后。test-util - Semver: 以下属于破坏性变更:移除或重命名公共项目;向未标记 的 enum 添加变体;向可由外部构造的 struct 添加字段;添加 trait bound;或使 trait 不再支持 object safety。在次要版本中先弃用,再在主要版本中移除。新 crate 应使用最新的稳定 edition,并明确设置 MSRV。
#[non_exhaustive] - 不要在 之前运行代码(不要使用
main()风格的 crate)。不要在稳定工具链上使用不稳定特性。ctor
10. Documentation and comments
10. 文档与注释
- on every public item;
///at the top of every public module and the crate root. The first sentence is a single line of about fifteen words, because it becomes the summary in listings.//! - Sections, in this order, as applicable: ,
# Examples,# Errors,# Panics. Examples use# Safetywith the?hidden behindfn mainlines, never#, because users copy them verbatim. Describe parameters in prose, not a table.unwrap() - Document what the signature can't say: preconditions, side effects, ownership
transfer, complexity, and why. Don't restate the type or the name;
on
/// Removes an element.is noise.remove_element - Link items with . Hide impls users never trigger (
[`Type`]) withFrom<PrivateError> for PublicError.#[doc(hidden)] - comments explain why, not what:
//,// SAFETY:, non-obvious magic values, workarounds with a link. No design journals, "why we chose X" essays, or rule-compliance tables in code docs. Document the end state.// TODO(issue-123):
- 每个公共项目都应使用 ;每个公共模块和 crate 根目录都应使用
///。第一句话应为约十五个单词的单行摘要,因为它会成为列表中的摘要。//! - 根据适用情况,按以下顺序组织章节:、
# Examples、# Errors、# Panics。示例应使用# Safety,并通过?行隐藏#,绝不要使用fn main,因为用户会原样复制示例。应以文字而非表格描述参数。unwrap() - 应记录签名无法表达的信息:前置条件、副作用、所有权转移、复杂度以及实现原因。不要重复类型或名称;为 编写
remove_element只是噪声。/// Removes an element. - 使用 链接项目。应隐藏用户永远不会触发的实现(
[`Type`]),方法是添加From<PrivateError> for PublicError。#[doc(hidden)] - 注释应解释原因,而不是复述代码行为:例如
//、// SAFETY:、不明显的魔法值,以及带有链接的临时解决方案。代码文档中不应包含设计日志、“我们为何选择 X”之类的长篇文章或规则对照表。只记录最终状态。// TODO(issue-123):
11. Tests
11. 测试
- Unit tests in for private internals. Integration tests in
#[cfg(test)] mod tests { use super::*; .. }for anything reachable through the public API, and prefer that location when either would do. Doc examples are tests too.tests/ - Test observable behavior and properties (monotonic, round-trips, idempotent), not restated constants or mirrored branches.
- Write a failing test before fixing a bug. Cover each feature combination and each platform-specific path.
- Anything doing I/O, time, randomness, or network must be mockable:
behind
new_mocked() -> (Self, MockCtrl), not atest-utilthat performs I/O.Default - Fuzz parsers of untrusted input with . Benchmark with
cargo fuzzorcriterionanddivan. Run Miri overstd::hint::black_box.unsafe - Assertions get context so failures read well: .
assert_eq!(got, want, "input {input:?}")
- 对于私有内部实现,使用 编写单元测试。对于任何可通过公共 API 访问的功能,使用
#[cfg(test)] mod tests { use super::*; .. }中的集成测试;如果两种方式均可,优先使用集成测试。文档示例同样是测试。tests/ - 应测试可观察行为和属性(单调性、往返转换、幂等性),而不是复述常量或镜像分支逻辑。
- 修复 bug 前先编写一个会失败的测试。覆盖每种 feature 组合以及每条平台专用路径。
- 任何涉及 I/O、时间、随机性或网络的功能都必须可 mock:通过 提供
test-util,而不是使用会执行 I/O 的new_mocked() -> (Self, MockCtrl)。Default - 使用 对不可信输入解析器进行模糊测试。使用
cargo fuzz或criterion进行基准测试,并使用divan。在std::hint::black_box代码上运行 Miri。unsafe - 断言应包含上下文,使失败信息易于理解,例如 。
assert_eq!(got, want, "input {input:?}")
12. Performance, in order
12. 性能:按顺序处理
Design for usability first and optimize after measuring. When you do:
or over push loops; reuse caller-owned buffers in hot APIs
(); , , for
long-lived immutable sequences; on long-lived collections built without a
reservation; a faster hasher (, ) only for trusted keys; batch work
instead of per-item locking; applications may set a such as
. Derive for small plain types and never for large ones.
Vec::with_capacitycollect()fn read_into(&mut self, buf: &mut Vec<u8>)Box<[T]>Box<str>Arc<str>shrink_to_fitfoldhashFxHash#[global_allocator]mimallocCopy首先围绕可用性进行设计,测量之后再进行优化。需要优化时:使用 或 ,而不是 push 循环;在热路径 API 中复用调用者拥有的缓冲区();对于长期存在且不可变的序列,使用 、、;对于未预留容量而构建的长期集合,使用 ;仅在密钥可信时使用更快的 hasher(、);批量处理工作,而不是逐项加锁;应用可以设置 ,例如 。为体积较小的普通类型派生 ,但绝不要为大型类型这样做。
Vec::with_capacitycollect()fn read_into(&mut self, buf: &mut Vec<u8>)Box<[T]>Box<str>Arc<str]shrink_to_fitfoldhashFxHash#[global_allocator]mimallocCopy13. Final checklist
13. 最终检查清单
Before returning Rust code, confirm:
- ,
cargo fmt, andcargo clippy -- -D warningsare clean.cargo test - No outside tests and
unwrap(); everymain,expect, andasserthas a message with the relevant values.panic - Public functions take ,
&str,&[T], orimpl AsRef<_>, notimpl IntoIteratoror&String.&Vec<T> - Every public type derives and the applicable common traits; error types implement
Debug.Error + Display + Send + Sync + 'static - No parameters in public signatures; no hand-written
boolwithout matchingOrdandPartialEq.Hash - No whose only job is to satisfy the borrow checker.
.clone() - No glob imports, no preludes, no glob re-exports, no leaked third-party types.
- Every block has a
unsafecomment; every// SAFETY:hasunsafe fndocs.# Safety - Public items have docs with a one-line summary and /
# Errorswhere relevant.# Panics - Application code stayed simple; library code followed the API rules.
返回 Rust 代码前,请确认:
- 、
cargo fmt和cargo clippy -- -D warnings均无问题。cargo test - 测试和 之外没有
main;每个unwrap()、expect和assert的消息中都包含相关值。panic - 公共函数接收 、
&str、&[T]或impl AsRef<_>,而不是impl IntoIterator或&String。&Vec<T> - 每个公共类型都派生 及适用的常见 trait;错误类型实现
Debug。Error + Display + Send + Sync + 'static - 公共签名中没有 参数;手写
bool时配有匹配的Ord和PartialEq。Hash - 不存在仅为满足 borrow checker 而调用的 。
.clone() - 没有 glob 导入、prelude、glob 重新导出或泄漏的第三方类型。
- 每个 block 都有
unsafe注释;每个// SAFETY:都有unsafe fn文档。# Safety - 公共项目都有单行摘要文档,并在相关位置提供 /
# Errors。# Panics - 应用代码保持简单;库代码遵循 API 规则。
References
参考资料
Read these when you need the reasoning behind a rule or a complete checklist for a task.
Each is a condensed digest of one source.
- (https://rust-lang.github.io/api-guidelines/): designing a public API or crate; all 54 checklist items with conventions.
references/rust-api-guidelines.md - (https://www.lurklurk.org/effective-rust/): choosing between alternatives: generics vs
references/effective-rust.md,dynvs panic, iterators vs loops, borrow-checker tactics, dependencies, CI.Result - (https://rust-unofficial.github.io/patterns/): idioms, patterns, and anti-patterns with code.
references/rust-design-patterns.md - (https://microsoft.github.io/rust-guidelines/): error struct design, panic policy, unsafe policy, async, workspace layout, performance.
references/microsoft-rust-guidelines.md - (https://google.github.io/styleguide/rustguide.html): formatting, imports, module layout, accessor naming, application vs library.
references/google-rust-style-guide.md
当需要了解某条规则背后的原因或某项任务的完整检查清单时,请阅读以下资料。每份资料都是对应来源的浓缩摘要。
- (https://rust-lang.github.io/api-guidelines/):设计公共 API 或 crate;包含全部 54 项检查清单及约定。
references/rust-api-guidelines.md - (https://www.lurklurk.org/effective-rust/):在替代方案之间进行选择,例如泛型与
references/effective-rust.md、dyn与 panic、迭代器与循环、borrow checker 技巧、依赖和 CI。Result - (https://rust-unofficial.github.io/patterns/):包含惯用法、设计模式和反模式及代码示例。
references/rust-design-patterns.md - (https://microsoft.github.io/rust-guidelines/):涵盖错误 struct 设计、panic 策略、unsafe 策略、async、workspace 布局和性能。
references/microsoft-rust-guidelines.md - (https://google.github.io/styleguide/rustguide.html):涵盖格式化、导入、模块布局、访问器命名,以及应用代码与库代码的差异。
references/google-rust-style-guide.md