Rust Ownership

rust是一门适合底层的通过编译来使用的语言。他有许多的特点,比如他是静态类型的、有代数类型、模式匹配、类型特征等等。但最具特色的应该是他的垃圾回收机制。他没有显式的内存分配和销毁过程也没有耗时的垃圾回收算法。取而代之的是变量的Ownership。下面是我个人的理解。

传统的垃圾回收机制中会对一个数据进行强引用计数,并且再计数为0的时候清除这些数据,这里涉及到对所有变量的扫描,需要很复杂的算法以提高其效率。而rust避开了这个问题。他强制规定一个数据只能有一个强引用,也就是ownership。这就会使问题得以简化,当变量被销毁(走出作用域)的时候他的数据也就自然而然被销毁了。没有显式的释放内存但效率却和显式释放一样高。

第一眼看到这个设计的时候我有一种天上掉馅饼的感觉,怎么啥都没做就获得了一个高效的垃圾回收策咯。但细思极恐。我想到的最主要的问题有

  1. 变量赋值时ownership会有怎么样的变化
  2. 函数调用时的参数和返回值的ownership会如何变化
  3. 闭包使用上下文变量时,上下文作用域已经被销毁了,如何处理这个问题

第一个问题也是官方书中的一个例子。当变量赋值时,ownership从一个变量move到另一个变量,原来的变量不可访问!如下面例子:

let s1 = String::from("hello world");
let s2 = s1;

s1赋值给s2s1也就不能访问了。这对于我来说非常的反直觉,也让我明白,高效的垃圾回收并不是免费的。这就是代价之一。但这个设定让我联想到了永生的问题:如果未来计算机强大到能模拟人脑的所有状态,那我把我的大脑数据上传到计算之后会产生两个能够思考的我。那计算机里那个灵魂永生和肉体的我到底有没有关系呢?在rust中就没有这种问题,因为s2的诞生意味着s1的结束。难道说rust是一门有灵魂的语言。。

第一个问题解决了第二个问题也就解决了,只需要把参数和返回值都想象成赋值,就可以了,但是却引出了更反直觉的事。那就是当我调用函数的时候,作为参数的变量失效了。如果需要在函数调用之后继续使用变量,则需要使用返回值把变量的ownership返回到函数调用外。这里,rust使用了引用来解决这个问题。引用类似于很多语言中的弱引用,对数据没有ownership,所以在引用变量销毁的时候不会对数据造成影响。

弱引用的一个问题在于无法控制数据的生命周期,会导致引用还在,但数据已经销毁的情况。rust提供了一种叫lifetime的机制来保证弱引用不会出现引用被释放数据的问题。在rust中,引用有两种模式:可变和不可变。在同一个作用域中只能存在一个可变引用或者多个不可变引用。更多内容可以参考文档。在这里我想说明的是对引用的赋值会出现什么情况:

{
  let s = String::from("hello world");
  let r1 = &s;
  let r2 = r1;
}
{
  let mut s = String::from("hello world");
  let r1 = &mut s;
  let r2 = r1;
}

不可变引用在赋值时是拷贝赋值,这也好理解,因为一个作用域中可以存在多个不可变引用,所以r1r2可以共存。而可变引用只能存在一个,在第二段中,r1把引用的值move到了r2

第三个问题rust也巧妙的解决了。rust中function是不允许使用上下文变量的,而闭包相当于一个把所有用到的上下文变量打包在一起的结构体。

rust引入了ownership的概念来高效处理垃圾回收,又用各种技巧把这些反直觉的副作用消除。但尽管这样,还是存在一些和之前的语言不同的特性。天下没有免费的午餐啊。