1. 所有权
所有权系统,即ORBM(Ownership Based Resource Management,基于所有权的资源管理)。要求资源和变量紧耦合,资源同一时间只能存在一个owner,owner负责自己管理的资源的释放。rust引入所有权的意义在于强制使用RAII管理资源。
rust的所有权机制是借助类型系统在编译期实现的。
1.1. move语义和所有权转移
move语义的解释:被move的资源保持不变,前一个owner的栈上内存被按位(by-bits)复制给后一个owner的栈上内存空间。同时,前一个owner的生命周期结束,处于uninitialized状态。
- move语义通过
=
运算符显式转移所有权。 - 或者通过传参隐式转移给函数参数。
- 或者通过函数返回值隐式返回。
1.2. copy语义
copy语义的解释:前一个owner以及它拥有的资源保持不变。前一个owner的栈上存储空间被按位(by-bits)clone一份后给后者,拥有的资源也被clone一份给后者,同时修改后者栈上空间的指针。
默认情况下,变量的绑定具有move语义。除非实现std::marker::Copy
trait,例如原始类型。
2. 生命周期
我的理解是:程序中有三个需要讨论生命周期的元素,即变量、引用和资源。强调rust生命周期这个概念的意义在于更好地理解rust要求程序遵守的约束,保证内存安全和线程安全。
rust的生命周期要求的规则也是借助编译器和borrow checker在编译期实现。
2.1. 变量
对于初始化了的变量,它的生命周期即它所在的local scope,当超出scope,需要释放:
- 自己绑定的内存空间
- 自己管理的资源。
这和所有权系统一致。
2.2. 引用
引用的生命周期需要满足下列两个约束:
- 引用的生命周期不能超过其引用的值的所有者。
- 引用的生命周期应当大于等于存储其值的变量的生命周期。
通过这两个约束,给出了引用生命周期的上下界。这样才能没有悬垂指针,内存安全。除此之外为了线程安全,引用还需要满足
- 多个不可变引用 xor 一个可变引用。
但这和生命周期关系不大。解决的是数据竞争的问题。
2.3. 资源
这里的资源指的是例如:堆内存,socket,文件描述符等可以move的实体,不包括和初始化的变量绑定的内存空间(栈上内存)。
2.4. 示例
给出如下记号
- 每一个变量和引用都有对应的生命周期
x['a]
: x的生命周期是'a
&'a
: 引用某个生命周期至少是'a
的变量
2.4.1. 示例一
1 |
|
2.4.2. 示例二
- borrow checker总是会采用尽可能小的生命周期
1 |
|
2.4.3. 示例三
1 |
|
如上,y
的生命周期为'c
,而其包含的引用的生命周期是'b
(最小化采用b
而非a
,不变式为:$c<=b<=b<a$)。
2.4.4. 使用不变式判断
1 |
|
2.4.5. 最小化生命周期的原因
以下是合法的几个例子。
1 |
|
1 |
|
可以发现,c的作用域从let c
开始,一直到末尾,然而c的声明周期仅为'b
。
2.5. 函数/方法的生命周期标注
1 |
|
上述代码,bad_fn
会自动引入作用域,且bad_fn
生命周期不能超过'a
2.6. 结构体的生命周期标注
1 |
|
表示,结构体中对i32
的引用的生命周期应当超过结构体实例。
3. Owned type: &str vs. String
类型不一定具有明确的size(表示成?Sized
),这意味着它们:
- 无法copy/move到内存中
- 它们的值仅能通过引用获取(
&T
) - 它们对应有owner类型(例如
String
是str
的拥有者类型),又被称为容器类型(container type)
以下是一些比较:
&str
和String
:String
是容器类型能够执行push,扩充内存等操作,&str
仅是切片类型,指向的资源长度一定。&str
和&String
:&String
是指向容器类型的引用,String
作为容器类型,位于栈上空间。&str
是对堆上空间的切片引用。str
和[u8; N]
:str
长度不定,[u8; N]
是数组,长度一定,可以存在于栈上。str
和[u8]
:本质上一样&str
和&[u8]
:本质上一样