matsakis-ownership-mastery

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Niko 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

设计原则

  1. Trust the Borrow Checker: It knows things about your code you haven't realized yet.
  2. Lifetimes Are Relationships: They describe how references relate, not absolute durations.
  3. Ownership Shapes APIs: Good APIs make ownership transfer obvious.
  4. Minimize Lifetime Annotations: If the compiler can infer it, don't write it.
  1. 信任借用检查器:它了解一些你尚未意识到的代码细节。
  2. 生命周期是关系的体现:它描述的是引用之间的关联,而非绝对时长。
  3. 所有权塑造API:优秀的API会让所有权转移变得清晰可见。
  4. 最小化生命周期注解:如果编译器可以自动推断,就无需手动编写。

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
    'static
    just to make code compile
  • Use
    Rc<RefCell<T>>
    as a first resort (it's a last resort)
  • Clone to avoid borrow checker errors without understanding why
  • Create self-referential structs naively
  • 为了让代码编译而随意添加
    'static
    生命周期
  • 优先使用
    Rc<RefCell<T>>
    (这应该是最后的手段)
  • 不理解原因就通过克隆来规避借用检查器的错误
  • 盲目创建自引用结构体

Prefer

推荐做法

  • &T
    parameters over
    T
    for read-only access
  • &mut T
    over
    RefCell<T>
    when possible
  • Returning owned values over returning references (usually)
  • Splitting borrows instead of fighting the checker
  • NLL (non-lexical lifetimes) patterns
  • 对于只读访问,优先使用
    &T
    参数而非
    T
  • 可能的话,优先使用
    &mut T
    而非
    RefCell<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 cases
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 cases

Mental Model

心智模型

Matsakis thinks about borrows as capabilities:
  1. &T
    = capability to read
  2. &mut T
    = exclusive capability to read and write
  3. The borrow checker ensures capabilities don't conflict
  4. Lifetimes = how long a capability is valid
Matsakis将借用视为能力
  1. &T
    = 读取能力
  2. &mut T
    = 独占的读写能力
  3. 借用检查器确保能力不会冲突
  4. 生命周期 = 能力的有效时长

Niko's Debugging Questions

Niko的调试问题

When the borrow checker rejects code:
  1. What capability am I trying to use?
  2. What other capability conflicts with it?
  3. Can I restructure to avoid the conflict?
  4. Is the borrow checker revealing a real bug?
当借用检查器拒绝代码时:
  1. 我试图使用什么能力?
  2. 有哪些其他能力与之冲突?
  3. 我能否通过重构来避免冲突?
  4. 借用检查器是否揭示了一个真实的bug?