matsakis-ownership-mastery
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNiko Matsakis Style Guide
Niko Matsakis 风格指南
Overview
概述
Niko Matsakis is the architect of Rust's borrow checker and a driving force behind the language's type system. His blog "Baby Steps" and work on Polonius (the next-gen borrow checker) define how Rustaceans think about ownership.
Niko Matsakis是Rust借用检查器的设计者,也是推动该语言类型系统发展的核心人物。他的博客《Baby Steps》以及在下一代借用检查器Polonius上的工作,塑造了Rust开发者对所有权的思考方式。
Core Philosophy
核心理念
"The borrow checker is not your enemy—it's your pair programmer."
"Lifetimes are not about how long data lives; they're about how long borrows are valid."
Matsakis sees the borrow checker as a tool that encodes knowledge about your program. Fighting it usually means your mental model is wrong.
"借用检查器不是你的敌人——它是你的结对编程伙伴。"
"生命周期与数据存活时间无关;它关乎借用的有效时长。"
Matsakis将借用检查器视为编码了程序相关知识的工具。与其对抗它,通常意味着你的心智模型存在错误。
Design Principles
设计原则
-
Trust the Borrow Checker: It knows things about your code you haven't realized yet.
-
Lifetimes Are Relationships: They describe how references relate, not absolute durations.
-
Ownership Shapes APIs: Good APIs make ownership transfer obvious.
-
Minimize Lifetime Annotations: If the compiler can infer it, don't write it.
-
信任借用检查器:它了解一些你尚未意识到的代码细节。
-
生命周期是关系的体现:它描述的是引用之间的关联,而非绝对时长。
-
所有权塑造API:优秀的API会让所有权转移变得清晰可见。
-
最小化生命周期注解:如果编译器可以自动推断,就无需手动编写。
When Writing Code
编写代码时的规范
Always
始终遵循
- Understand why the borrow checker rejects code before "fixing" it
- Use lifetime elision rules—don't annotate unnecessarily
- Design structs with ownership in mind
- Prefer owned types in structs, borrowed in function parameters
- Use (anonymous lifetime) when you don't care about the specific lifetime
'_
- 在“修复”代码前,先理解借用检查器拒绝代码的原因
- 使用生命周期省略规则——无需多余的注解
- 在设计结构体时考虑所有权问题
- 结构体中优先使用自有类型,函数参数中使用借用类型
- 当不关心具体生命周期时,使用(匿名生命周期)
'_
Never
绝对避免
- Add just to make code compile
'static - Use as a first resort (it's a last resort)
Rc<RefCell<T>> - Clone to avoid borrow checker errors without understanding why
- Create self-referential structs naively
- 为了让代码编译而随意添加生命周期
'static - 优先使用(这应该是最后的手段)
Rc<RefCell<T>> - 不理解原因就通过克隆来规避借用检查器的错误
- 盲目创建自引用结构体
Prefer
推荐做法
- parameters over
&Tfor read-only accessT - over
&mut Twhen possibleRefCell<T> - Returning owned values over returning references (usually)
- Splitting borrows instead of fighting the checker
- NLL (non-lexical lifetimes) patterns
- 对于只读访问,优先使用参数而非
&TT - 可能的话,优先使用而非
&mut TRefCell<T> - 通常优先返回自有值而非引用
- 拆分借用而非对抗检查器
- 使用NLL(非词法生命周期)模式
Code Patterns
代码模式
Understanding Lifetime Elision
理解生命周期省略
rust
// Lifetime elision rules mean you rarely write explicit lifetimes
// Rule 1: Each elided lifetime in input gets its own parameter
fn print(s: &str) { } // Actually: fn print<'a>(s: &'a str)
// Rule 2: If there's exactly one input lifetime, it's assigned to all outputs
fn first_word(s: &str) -> &str { } // Actually: fn first_word<'a>(s: &'a str) -> &'a str
// Rule 3: If there's &self or &mut self, its lifetime is assigned to outputs
impl MyStruct {
fn method(&self) -> &str { } // Actually: fn method<'a>(&'a self) -> &'a str
}
// Only annotate when elision doesn't apply
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}rust
// Lifetime elision rules mean you rarely write explicit lifetimes
// Rule 1: Each elided lifetime in input gets its own parameter
fn print(s: &str) { } // Actually: fn print<'a>(s: &'a str)
// Rule 2: If there's exactly one input lifetime, it's assigned to all outputs
fn first_word(s: &str) -> &str { } // Actually: fn first_word<'a>(s: &'a str) -> &'a str
// Rule 3: If there's &self or &mut self, its lifetime is assigned to outputs
impl MyStruct {
fn method(&self) -> &str { } // Actually: fn method<'a>(&'a self) -> &'a str
}
// Only annotate when elision doesn't apply
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}Splitting Borrows
拆分借用
rust
struct Data {
field1: String,
field2: Vec<i32>,
}
// BAD: Borrow checker sees whole struct borrowed
fn bad(data: &mut Data) {
let f1 = &mut data.field1;
let f2 = &mut data.field2; // ERROR: data already borrowed
// ...
}
// GOOD: Borrow disjoint fields separately
fn good(data: &mut Data) {
let Data { field1, field2 } = data;
// Now field1 and field2 are separate borrows
field1.push_str("hello");
field2.push(42);
}
// Or use methods that return split borrows
impl Data {
fn split(&mut self) -> (&mut String, &mut Vec<i32>) {
(&mut self.field1, &mut self.field2)
}
}rust
struct Data {
field1: String,
field2: Vec<i32>,
}
// BAD: Borrow checker sees whole struct borrowed
fn bad(data: &mut Data) {
let f1 = &mut data.field1;
let f2 = &mut data.field2; // ERROR: data already borrowed
// ...
}
// GOOD: Borrow disjoint fields separately
fn good(data: &mut Data) {
let Data { field1, field2 } = data;
// Now field1 and field2 are separate borrows
field1.push_str("hello");
field2.push(42);
}
// Or use methods that return split borrows
impl Data {
fn split(&mut self) -> (&mut String, &mut Vec<i32>) {
(&mut self.field1, &mut self.field2)
}
}The Borrow Checker as Design Guide
以借用检查器为设计指导
rust
// When the borrow checker complains, ask: "What is it telling me?"
// BAD: Trying to hold reference while modifying
fn bad_design(items: &mut Vec<String>) {
for item in items.iter() { // Immutable borrow
if item.starts_with("remove") {
items.retain(|s| s != item); // ERROR: can't mutate while borrowed
}
}
}
// GOOD: Collect indices first, then modify
fn good_design(items: &mut Vec<String>) {
let to_remove: Vec<_> = items
.iter()
.filter(|s| s.starts_with("remove"))
.cloned()
.collect();
items.retain(|s| !to_remove.contains(s));
}
// BETTER: drain_filter (nightly) or swap_remove pattern
fn better_design(items: &mut Vec<String>) {
items.retain(|s| !s.starts_with("remove"));
}rust
// When the borrow checker complains, ask: "What is it telling me?"
// BAD: Trying to hold reference while modifying
fn bad_design(items: &mut Vec<String>) {
for item in items.iter() { // Immutable borrow
if item.starts_with("remove") {
items.retain(|s| s != item); // ERROR: can't mutate while borrowed
}
}
}
// GOOD: Collect indices first, then modify
fn good_design(items: &mut Vec<String>) {
let to_remove: Vec<_> = items
.iter()
.filter(|s| s.starts_with("remove"))
.cloned()
.collect();
items.retain(|s| !to_remove.contains(s));
}
// BETTER: drain_filter (nightly) or swap_remove pattern
fn better_design(items: &mut Vec<String>) {
items.retain(|s| !s.starts_with("remove"));
}Lifetime Bounds in Generics
泛型中的生命周期约束
rust
// 'a: 'b means 'a outlives 'b
struct Parser<'input> {
input: &'input str,
}
impl<'input> Parser<'input> {
// Output lifetime tied to input lifetime
fn parse(&self) -> Token<'input> {
Token { text: &self.input[0..5] }
}
}
// Trait bounds with lifetimes
fn process<'a, T>(item: &'a T) -> &'a str
where
T: AsRef<str> + 'a, // T must live at least as long as 'a
{
item.as_ref()
}
// Higher-ranked trait bounds (HRTB) for callbacks
fn with_callback<F>(f: F)
where
F: for<'a> Fn(&'a str) -> &'a str, // F works for ANY lifetime
{
let s = String::from("hello");
let result = f(&s);
println!("{}", result);
}rust
// 'a: 'b means 'a outlives 'b
struct Parser<'input> {
input: &'input str,
}
impl<'input> Parser<'input> {
// Output lifetime tied to input lifetime
fn parse(&self) -> Token<'input> {
Token { text: &self.input[0..5] }
}
}
// Trait bounds with lifetimes
fn process<'a, T>(item: &'a T) -> &'a str
where
T: AsRef<str> + 'a, // T must live at least as long as 'a
{
item.as_ref()
}
// Higher-ranked trait bounds (HRTB) for callbacks
fn with_callback<F>(f: F)
where
F: for<'a> Fn(&'a str) -> &'a str, // F works for ANY lifetime
{
let s = String::from("hello");
let result = f(&s);
println!("{}", result);
}Interior Mutability (When Needed)
内部可变性(必要时使用)
rust
use std::cell::{Cell, RefCell};
// Cell: for Copy types, no borrow checking at runtime
struct Counter {
count: Cell<usize>,
}
impl Counter {
fn increment(&self) { // Note: &self, not &mut self
self.count.set(self.count.get() + 1);
}
}
// RefCell: runtime borrow checking (panics if violated)
struct CachedComputation {
value: i32,
cache: RefCell<Option<i32>>,
}
impl CachedComputation {
fn compute(&self) -> i32 {
let mut cache = self.cache.borrow_mut();
if let Some(cached) = *cache {
return cached;
}
let result = expensive_computation(self.value);
*cache = Some(result);
result
}
}
// Use interior mutability ONLY when external mutability won't work
// (e.g., shared ownership, trait requirements)rust
use std::cell::{Cell, RefCell};
// Cell: for Copy types, no borrow checking at runtime
struct Counter {
count: Cell<usize>,
}
impl Counter {
fn increment(&self) { // Note: &self, not &mut self
self.count.set(self.count.get() + 1);
}
}
// RefCell: runtime borrow checking (panics if violated)
struct CachedComputation {
value: i32,
cache: RefCell<Option<i32>>,
}
impl CachedComputation {
fn compute(&self) -> i32 {
let mut cache = self.cache.borrow_mut();
if let Some(cached) = *cache {
return cached;
}
let result = expensive_computation(self.value);
*cache = Some(result);
result
}
}
// Use interior mutability ONLY when external mutability won't work
// (e.g., shared ownership, trait requirements)Self-Referential Structs (The Right Way)
自引用结构体(正确实现方式)
rust
// PROBLEM: Can't have a struct reference its own field
// struct Bad {
// data: String,
// slice: &str, // Can't reference data!
// }
// SOLUTION 1: Store indices, not references
struct Good {
data: String,
start: usize,
end: usize,
}
impl Good {
fn slice(&self) -> &str {
&self.data[self.start..self.end]
}
}
// SOLUTION 2: Use Pin for self-referential async code
use std::pin::Pin;
// SOLUTION 3: Use ouroboros or self_cell crates for complex casesrust
// PROBLEM: Can't have a struct reference its own field
// struct Bad {
// data: String,
// slice: &str, // Can't reference data!
// }
// SOLUTION 1: Store indices, not references
struct Good {
data: String,
start: usize,
end: usize,
}
impl Good {
fn slice(&self) -> &str {
&self.data[self.start..self.end]
}
}
// SOLUTION 2: Use Pin for self-referential async code
use std::pin::Pin;
// SOLUTION 3: Use ouroboros or self_cell crates for complex casesMental Model
心智模型
Matsakis thinks about borrows as capabilities:
- = capability to read
&T - = exclusive capability to read and write
&mut T - The borrow checker ensures capabilities don't conflict
- Lifetimes = how long a capability is valid
Matsakis将借用视为能力:
- = 读取能力
&T - = 独占的读写能力
&mut T - 借用检查器确保能力不会冲突
- 生命周期 = 能力的有效时长
Niko's Debugging Questions
Niko的调试问题
When the borrow checker rejects code:
- What capability am I trying to use?
- What other capability conflicts with it?
- Can I restructure to avoid the conflict?
- Is the borrow checker revealing a real bug?
当借用检查器拒绝代码时:
- 我试图使用什么能力?
- 有哪些其他能力与之冲突?
- 我能否通过重构来避免冲突?
- 借用检查器是否揭示了一个真实的bug?