本文是对Programming Rust 第4章的总结,部分代码与图片来自此书
背景
我们通常希望变成语言提供以下特性:
- 我们希望可以控制内存释放的时机
- 我们不希望使用一个对象已经被释放的指针
c/c++与带gc的语言通过不同的机制解决以上问题,但是都各自有不同的缺点
rust使用了Ownership与Borrow来解决内存管理的问题
Ownership
- 在rust中一个变量被赋值的时候就拥有了这个值的所有权,一个
Struct拥有字段的所有权, 一个Tuple,Array,Vec拥有包含元素的所有权。 - 一个值只能被一个变量拥有, 这个变量会对此值的生命周期负责, 当一个变量被
Drop的时候 它所拥有的所有值也会一起被销毁
以下代码创建了一个Vec, 它包含了多个结构体
| |
它的内存布局为

在很多情况下,这种一对一的绑定关系显然缺乏灵活性, 在真实的编程当中,对象之间的互相引用,多个对象引用同一个对象是很常见的(比如双向链表, 树结构等等)。所以rust在所有权原则的基础上提供很多其他机制来达成这种灵活性
Move
在rust中,像赋值,对函数传值,函数返回值等操作会使一个变量转移它对值得所有权,在一个变量转移所有权之后, 它会变成未初始化状态, 后续得代码无法访问该变量。比如以下代码
| |
在内存中的布局如下所示

s在把值转移给t后变为未初始化状态,同时t只是对s的值本身(Value Proper)做了赋值。这里的Value Proper我理解的意思就是,大部分情况就是一个值的栈上数据, 因为栈上数据不需要做内存管理;当然肯定存在堆上数据做Move的情况,总的来说编译器会做一次浅复制Move操作保证了一个值始终只有一个拥有者,所以有且只有一个人会对这个值的内存管理负责
控制流中的Move
rust编译器会根据条件语句来判断,一个值是否被Move,比如
if/else语句
| |
循环语句
| |
数组中的Move
一个Vec V中的元素属于V, 不能将他Move给其他变量
| |
- 当然了一个
Vec必须可以删除元素, 比如移除队尾数据, 移除数据并且使用队尾填充, 还有著名的mem::replace, 这些方法都遵循一个原则就是保证vec在一个连贯的状态,使得Vec只在记录length的情况下就能维护本身的状态。 由于对外赋值会导致Move, 所以一般赋值操作可以使用Borrow或者Clone
| |
值得一提的是 ,一个
Vec使用remove方法删除index处的元素时, 所有右边的元素都会左移,这个操作消耗是很大的。不过这个东西跟rust无关, 所有语言都是这么实现另外在一个for循环中, 我们会将整个
Vec转移出去, 并且将他打散成各个元素, 后续不管这些元素有没有被再次转移, 整个初始Vec已经不可见了
| |
Copy
在rust中对于简单类型,也使用Move语义是没有必要的, 因为简单类型除了自己的值本身之外并没有要额外管理的资源,这个资源可能是堆内存,可能是文件句柄等。这个时候对一个变量赋值应该是直接Copy就可以了, 事实上他跟Move就仅仅是是否清除原变量的值本身,原变量是否保持可用
Copy类型包含了
- 整数,浮点,字符,布尔,指针等
Copy类型组成的TupleCopy类型组成的Array- 指定了
Copy的Struct - 指定了
Copy的枚举 - 所有复制只需要位拷贝的类型
不包含
- 所有
Drop的时候需要额外处理的都不是Copy类型 - 默认的`Struct
- 默认的
Enum
Rc和Arc
有了Move跟Copy之后,值的使用仍然比较受限, 很多时候我们希望可以有多个变量持有同一个值,就像其他语言一样。这个时候Rc就派上用场了,一个Rc就是在遵循前面几种规则的基础上,创造出的一种引用计数类型。
- 按照
Ownership的原则, 是不允许多个变量同时持有一个值T的。那么怎么做呢?答案就是把值T包装到一个容器里, 容器本身遵循Ownership, 容器内部来决定何时drop关联的值T, 这里这个容器就是Rc. Rc的共享使用Clone来实现,Clone操作只是对引用计数+1, 当变量被Drop时引用技术-1,当引用技术变为0的时候, Rc所包装的值T被释放Rc所包装的值T是不可变的, 这个符合单个可变与多个共享之间的排斥性,某些情况下可以使用内部可变性容器来解决这个问题, 比如Cell
| |
以下是Rc的内存布局
