Rust所有权与生命周期

110 阅读3分钟

所有权是 Rust 的突破性特性。它使 Rust 能够完全保证内存安全和高效,同时避免垃圾回收。在详细了解所有权系统之前,我们将考虑这种设计的动机。

我们假设您接受垃圾回收 (GC) 并不总是最佳解决方案,并且在某些情况下手动管理内存是可取的。如果您不接受这一点,或许我对其他语言更感兴趣?

无论您对 GC 的看法如何,很明显,它对代码安全是一个巨大的福音。您永远不必担心东西过早消失(尽管您是否仍然想指向该东西是另一个问题……)。这是一个普遍存在的问题,C 和 C++ 程序需要处理。考虑一下这个简单的错误,我们所有使用过非 GC 语言的人都曾经犯过:

fn as_str(data: &u32) -> &str {
    // 计算字符串
    let s = format!("{}", data);

    // 这里返回了一个只存在于此函数中的东西的引用!
    // 悬垂指针!释放后使用!
    // (这在 Rust 中无法编译)
    &s
}

这正是 Rust 的所有权系统旨在解决的问题。Rust 知道 &s 存在的范围,因此可以防止它逃逸。然而,这是一个简单的例子,即使是 C 编译器也可能能够捕获。随着代码变得越来越大,指针被传递到各种函数中,情况变得更加复杂。最终,C 编译器会失效,并且无法执行足够的逃逸分析来证明您的代码是不健全的。因此,它将被迫接受您的程序,并假设它是正确的。

这永远不会发生在 Rust 中。程序员有责任向编译器证明一切都是健全的。

当然,Rust 关于所有权的故事远比仅仅验证引用不会逃脱其指代对象的范围要复杂得多。这是因为确保指针始终有效比这复杂得多。例如,在这段代码中,

let mut data = vec![1, 2, 3];
// 获取一个内部引用
let x = &data[0];

// push 导致 data 的后备存储被重新分配。
// 悬空指针!释放后使用!
// (这在 Rust 中不会编译)
data.push(4);

println!("{}", x);

naive scope分析不足以防止这个错误,因为 data 实际上存在的时间和我们需要的时间一样长。但是,当我们在其中有一个引用时,它被更改了。这就是为什么 Rust 要求任何引用都冻结被引用对象及其所有者的原因。

References

有两种引用类型:

  • 共享引用:&
  • 可变引用:&mut

它们遵循以下规则:

  • 引用不能比它所引用的对象活得更久
  • 可变引用不能被别名化

就是这样。这就是引用所遵循的整个模型。

当然,我们应该定义别名化的含义。

error[E0425]: cannot find value aliased in this scope
--> <rust.rs>:2:20
|
2 | println!("{}", aliased);
| ^^^^^^^ not found in this scope

error: aborting due to previous error

⚠️ 不幸的是,Rust 实际上并没有定义它的别名模型。

用下一节讨论通常的别名是什么,以及它为什么重要。