浅析 Rust 所有权设计
前言
市面上的 Rust 书已经把基础说的足够好了,我没必要再搬砖,而是尝试从一些个人的角度来重新审视其中的一些设计。Rust 我认为最为关键的设计:所有权和借用。我们都知道 Rust 和 Go、Java 相比最大的不同就是没有 GC,那么没有 GC 的设计需要付出一定的代价,这个代价一部分就体现在今天要说的这两个设计中。
代价 1: 所有权
三规
- 每一个值都被一个变量所拥有 Each value in Rust has a variable that’s called its owner
- 一个值同时只能被一个变量所拥有 There can only be one owner at a time
- 当所有者离开作用域时,这个值将被丢弃 When the owner goes out of scope, the value will be dropped
很多基础都会提到这三个规定,非常重要。理解它就能理解所有权。而反过来,当我看完之后,反过来,其实可以从中推导它的设计。
问答推导
我还是习惯称呼为对象,所以我下面以对象称呼。
提问:GC 的目标是什么?
回答:垃圾对象,无用的对象
提问:垃圾怎么产生的?
回答:对象被创建了,但是后来它没用了
提问:怎么知道它没用了?
回答:我怎么知道?!😡 不就是没人用了么?
很好,其实到此,我们就已经知道了要解决的问题是什么了。接下来让我们一步步从后往前推导:
- 我不想要 GC,那我首先必须能有办法丢弃掉不用的垃圾对象,我觉得 “作用域” 是一个很不错的分界点,出了作用域我就把对象给扔掉 (规则 3)
- 当一个对象有多个用户(所有者/变量) 如:a1 = s; a2 = s; 那么此时离开作用域时,我会同时想要释放两次,并且这两次还是同一个对象,这不合理,我得限制让一个值只能有一个变量 (规则 12)
结论
这样推导下来其实 Rust,也没有特别复杂,它只不过将 GC 的回收工作放到了作用域结束,强制在离开时回收掉。
而原本 GC 要确定一个对象是否已经为垃圾,常用的方式是引用计数对吧?而 Rust 直接强制只有一个所有者,你没了就是你没了,和别人无关。直接简化了整个逻辑。
代价 2: 借用
由于所有权的唯一性,那我们在传递的时候势必就需要做一个操作,那就是转移。一直转来转去很麻烦,有没有别的办法呢?第一想法其实就是传指针,也就是搞引用。所以,Rust 弄了一个借用的功能出来。(注意在 Rust 中,“借用”和“引用”是一个概念)
借用不是就有多个人在用了吗?
这是我们遇到的第一个疑问,如果一个对象有多个引用,这不是又回到老路上,并且和所有权的规则向违背了吗?非也非也。关键点来了:
所有权,所有权,是证明东西是我的;借用,借用,东西还是你的,我借一下而已,还会还给你的。
这也是为什么称为借用(Borrow) 的原因,其实所有权本身并没有发生转移。
借用得有规则
借用也不能无条件的乱用,要有规则,而它的规则,我们并不用死记。懂并发吗?读写锁应该有了解吧?没错和读写锁的逻辑其实是一致的。
- 能有多个不可变的引用(能同时多读)
- 可变引用与不可变引用不能同时存在(不能同时读写)
- 同一作用域,特定数据只能有一个可变引用(只能有一个写)
那这个解决了什么问题呢?并发问题?不是,应该说 数据竞争 问题。
总结
为了没有 GC,设计了所有权作为代价;为了解决单一所有权的转移问题,设计了借用规则作为代价。而借用规则的可变不可变,也正是 Rust 设计中 mut 的理念的一种体现吧。所以总的来说,整个设计是环环相扣的剧情,其实并不复杂,理解本质原因,其实就能理解其中的设计了。