所有权和生命周期
所有权是 Rust 的突破性特性。它使 Rust 能够完全内存安全和高效,同时避免垃圾回收。在详细了解所有权系统之前,我们将考虑这种设计的动机。
我们将假设你接受垃圾回收 (GC) 并不总是最佳解决方案,并且在某些情况下手动管理内存是可取的。如果你不接受这一点,我能否向你推荐另一种语言?
无论你对 GC 的看法如何,它显然是使代码安全的一个巨大优势。你永远不必担心事情太早消失(尽管你是否仍然希望指向那个东西是另一个问题...)。这是一个 C 和 C++ 程序需要处理的普遍问题。考虑一下我们所有使用过非 GC 语言的人都犯过的一个简单错误:
#![allow(unused)] fn main() { fn as_str(data: &u32) -> &str { // compute the string let s = format!("{}", data); // OH NO! We returned a reference to something that // exists only in this function! // Dangling pointer! Use after free! Alas! // (this does not compile in Rust) &s } }
这正是 Rust 的所有权系统旨在解决的问题。Rust 知道 &s
的生命周期范围,因此可以防止它逃逸。然而,这是一个简单的情况,即使是 C 编译器也可能捕获到。随着代码变得更大,指针通过各种函数传递,事情变得更加复杂。最终,C 编译器会失效,并且无法执行足够的逃逸分析来证明你的代码是健全的。因此,它将被迫接受你的程序,假设它是正确的。
这永远不会发生在 Rust 中。程序员有责任向编译器证明一切都是健全的。
当然,Rust 关于所有权的故事远比仅仅验证引用没有超出其引用对象的范围要复杂得多。这是因为确保指针始终有效比这复杂得多。例如,在这段代码中,
#![allow(unused)] fn main() { let mut data = vec![1, 2, 3]; // get an internal reference let x = &data[0]; // OH NO! `push` causes the backing storage of `data` to be reallocated. // Dangling pointer! Use after free! Alas! // (this does not compile in Rust) data.push(4); println!("{}", x); }
朴素的作用域分析不足以防止此错误,因为 data
实际上在其需要的生命周期内存在。然而,当我们有一个指向它的引用时,它被改变了。这就是为什么 Rust 要求任何引用都冻结被引用对象及其所有者。