原文链接:https://doc.rust-lang.org/nomicon/ownership.html

所有权和生命周期

所有权是Rust的一个突破性功能。它让Rust可以彻底告别垃圾回收,同时做到内存安全和高效率。在涉及到所有权系统的细节之前,我们先看一下这种设计的目的。

我们假设你认同垃圾回收器(GC)不总是内存管理的最佳方案,在一些场景中需要手工地管理内存。如果你并不这么认为,那么请出门右转使用其他的语言吧。

但是,无论你怎么看待GC,它确实是保证代码安全的大杀器。你永远不需要担心有什么内容会被过早释放(尽管有的时候你已经不想再使用它们了……)。这是C和C++会普遍遇到的问题。看一下这个曾纠缠过每一个使用过非GC语言的人的简单错误:

  1. fn as_str(data: &u32) -> &str {
  2. // 计算字符串
  3. let s = format!("{}", data);
  4. // 哎呀!我们返回了一个只在函数内部存在的东西的引用
  5. // 悬垂指针!释放后引用!指针别名!
  6. // (当然这段代码在Rust中不能编译)
  7. &s
  8. }

这正是Rust的所有权系统要解决的问题。Rust知道&s生效的作用域,所以可以避免出现逃逸。不过这个例子太简单了,哪怕是C的编译器也可能捕捉到其中的错误。但是当代码量越来越大,指针来自四面八方的不同的函数时,事情就变得复杂了。C编译器最终会败下阵来,无法作出充分的逃逸分析来判断你的代码是否足够健壮。它能做的只有假设你的程序是正确的从而接受它。

这种事情永远不会发生在Rust的世界。Rust需要程序员向编译器保证自己代码的健壮性。

当然,Rust所有权系统要做的事有很多,不是仅仅验证引用不会超出被引用内容作用域这么简单。这是因为保证指针有效的条件比这个要复杂得多。以下面的代码为例。

  1. let mut data = vec![1, 2, 3];
  2. // 获得内部引用
  3. let x = &data[0];
  4. // 哎呀!push方法导致data的内部存储位置重新分配了
  5. // 悬垂指针!释放后引用!指针别名!
  6. // (当然这段代码在Rust中不能编译)
  7. data.push(4);
  8. println!("{}", x);

简单地分析作用域不足以防止这个bug,因为data在我们使用它的范围内确实是一直存在的。但是它在我们引用它的同时发生了变化。这就是为什么Rust要求引用的存在要锁定被引用内容和它的owner。