Rust 借用检查器简单解释 | AI生成和翻译

Home 2026.03

Rust Borrow Checker —— 全面介绍


1. 什么是 Borrow Checker?

Borrow checker 是 Rust 编译器的一个静态分析组件,它会验证程序中的每一个引用。它的任务是防止其他系统语言中常见的几种内存错误,例如 use-after-free(在内存被释放后访问它)和 data races。

当我们查看编译阶段——解析、语义分析(borrow checker 在此阶段发挥作用)、优化和代码生成——borrow checker 在语义分析阶段运行,在生成任何可执行二进制文件之前进行。

Borrow checker 执行的所有分析都在编译时完成。您不会为此支付任何运行时成本。然而,这个系统确实有一定的成本:学习曲线。许多 Rust 新手用户会经历一种称为“与 borrow checker 斗争”的情况,此时 Rust 编译器拒绝编译作者认为有效的程序。


2. Ownership —— 基础

Borrow checker 帮助您管理 ownership。正如 The Rust Programming Language 一书所述,“Ownership 是 Rust 最独特的功能,它使 Rust 能够在不需要垃圾回收器的情况下提供内存安全保证。”

在实践中,当传递变量时,您可以做三件事之一:移动数据本身并放弃 ownership,创建数据的副本并传递它,或者传递数据的引用并保留 ownership——让接收者暂时 borrow 它。


3. Borrowing 和 References

与其直接传递值,您可以获取一个引用(&T)。它不拥有资源,而是 borrow ownership。一个 borrow 某物的绑定在超出作用域时不会释放资源。这意味着在使用引用调用函数后,您可以再次使用原始绑定。

有三种方式访问 Rust 项的内容:通过项的所有者(item)、共享引用(&item)或可变引用(&mut item)。所有者可以创建、读取、更新和 drop 该项。可变引用可以读取和更新底层项。普通引用只能从中读取。


4. 两大核心规则

Borrow checker 强制执行两条基本规则:

规则 1:引用不能比它借用的值存活得更久

Rust 的 borrow checker 对借用值的方式施加了约束。引用不能比它借用的值存活得更久——例如,您不能存储一个指向超出作用域的值的引用,然后再尝试使用该引用。

规则 2:别名规则(Shared XOR Mutable)

对于给定的值,在任何时候:您可以有一个或多个对该值的共享引用,或者恰好有一个独占的(可变)引用到该值——绝不能同时两者都有。

fn main() {
    let mut a = 10;
    let b = &a;       // immutable borrow
    {
        let c = &mut a;   // mutable borrow — ERROR: conflicts with b
        *c = 20;
    }
    println!("{}", b); // b used here
}

上面的代码失败是因为 a 同时被可变借用(通过 c)和不可变借用(通过 b)。


5. 为什么别名规则重要:Data Races

Data race 发生在两个或多个指针同时访问同一内存位置,其中至少有一个在写入,并且操作未同步时发生。对于共享引用,您可以拥有任意多个,因为它们都不在写入。但是,由于您一次只能有一个 &mut T,因此不可能发生 data race。这是 Rust 如何在编译时防止 data races。


6. Iterator 失效预防

Borrow checker 防止的一个例子是“iterator invalidation”,即尝试变异您正在迭代的集合时发生的情况。如果您尝试在用不可变引用迭代 Vec 时向其 push,编译器会发出错误:cannot borrow 'v' as mutable because it is also borrowed as immutable


7. Lifetimes

Lifetimes 是 borrow checker 跟踪引用有效期长短的机制。

每当您创建一个 borrow,编译器就会为生成的引用分配一个 lifetime。这个 lifetime 对应于引用可以使用的那段代码范围。编译器推断这个 lifetime 为仍涵盖引用所有使用的最小 lifetime。

当编译器无法推断输入和输出 lifetimes 之间的关系时,需要在函数签名中使用显式 lifetime 注解:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

这里的 'a 表示:“返回的引用至少与两个输入一样长寿。”


8. Non-Lexical Lifetimes (NLL)

通过在 Rust 2018 中引入的 Non-Lexical Lifetimes (NLL),borrow checker 变得更灵活和直观。Rust 现在可以检测引用在作用域中真正不再被使用的时间,而不是强制它们存活到词法块结束。这减少了“borrow checker 斗争”,并确保您只关注真正的 lifetime 冲突。

在 NLL 之前,即使先前的不可变 borrow 已在变异之前结束,编译器也会抱怨您无法可变借用数据。有了 NLL,编译器会跟踪引用的实际使用位置,并在可能时更早结束它们的 lifetime。

从 Rust 1.63 开始,NLL 默认启用于所有 Rust editions,完成了旧的基于 AST 的 borrow checker 的移除。


9. Struct 中的字段 Borrowing

Struct 的字段可以独立于彼此被 borrow,但对 struct 调用方法会 borrow 整个 struct,可能使对单个字段的引用失效。


10. 逃生舱:Interior Mutability

当单写入者规则过于严格时,Rust 提供了 interior mutability 模式:

对于从 C++ 转过来的程序员,最常见的工具是 Rc<T>(及其线程安全变体 Arc<T>),通常与 RefCell(或线程安全的替代 Mutex)结合使用。然而,这种方法意味着您会失去 Rust 的一些保护——同一项在存在另一个引用时被可变借用的情况会导致运行时 panic 而非编译时错误。


11. 自引用 Struct —— 已知限制

来自其他语言的程序员与 borrow checker 的一个特定战斗是尝试创建自引用数据结构——包含 owned 数据及其内部引用的 struct。Rust 中的数据结构可以从栈移动到堆再返回,这使得内部引用本质上不安全。


12. 未来:Polonius

Rust borrow checking 的下一个前沿是 Polonius —— 从 2018 年的 NLL 工作中分离出的下一代 borrow checker。它的最重要贡献是修复已知限制,即 borrow checker 拒绝涉及条件返回引用的有效代码。


13. 总结表格

Concept Rule
Ownership 每个值恰好有一个所有者
Move Ownership 转移;原始绑定变为无效
Immutable borrow (&T) 可同时允许多个
Mutable borrow (&mut T) 一次仅一个;不允许其他借用
Lifetime 跟踪引用有效期长短
NLL 在最后使用处结束借用,而非作用域边界
Interior mutability 通过 RefCellMutex 等运行时检查变异

14. 与 Borrow Checker 协作的提示

当遇到 borrow checker 错误时,仔细阅读错误消息——Rust 的错误会解释违反了哪条规则,并经常建议修复。识别冲突的借用,追踪 lifetimes,并应用如 cloning、重组或 interior mutability 等模式。大多数时候,borrow checker 错误表明设计可以改进,而无需立即求助 unsafeRefCell

References:


Back Donate