Rust的一大特色(甚至在官方教程中 被称为the most unique feature )就是其借用及所有权机制。这两个机制由编译器进行强制,并且可以极大限度地保证变量安全(并行安全)。
所有权
前面说过,同C/C++一样,Rust中区分堆和栈(但不直接提及)。C/C++中语义上禁止从函数中返回指向局部变量的指针,是因为当离开函数时,栈会被回收,所以局部变量也会被回收,导致指针失效。
在C/C++中,该禁止并非语法上的禁止。而在Rust中,所有权机制将该概念显式表达,编译器会拒绝编译不满足所有权机制的代码。并且Rust设计者们希望通过所有权机制让程序员不需要考虑堆栈,只需要考虑所有权即可(应当是出于一致性以及或许对未来自动优化的考量?)。
Rust中的 寿元 机制也涉及堆栈的区别。 两者共同使得Rust不需要GC也可以保证内存安全。
所有权机制实际上就是三条规则:
每个值具有唯一的所有者变量
在同一时刻,所有者唯一
所有者离开作用域时,值会被丢弃
或者说(我个人的记法):每个值在每个时刻只有唯一的所有者,且有效性跟随所有者。
所有权本身不涉及特殊语法,只要记住上面三条规则即可。 官方教程所有权规则部分 中有例子可供参考。
切换变量时的所有权转移及保留
当将一个变量的值传给另一变量时(如赋值或函数调用时),有可能涉及所有权的变更。 Rust中与此相关的是move(移动语义)、copy(浅复制)和clone(深复制)。
和C++11中move的结果一样,Rust中将变量 a
传给另一变量 b
时,会将变量 a
中的值移动到变量 b
中去。这时,值的所有权从变量 a
转移到了变量 b
。该行为在赋值( a=b
)或函数调用传参时会自动发生,除非其右值符合copy或clone的条件(见下)。
当一个类型实现了 Clone
trait时,Rust会对其进行深复制。
当一个类型实现 Copy
trait时,Rust会对其进行浅复制。注意实现 Copy
trait需要该struct的所有成员也都实现了 Copy
,且该struct不能实现 Drop
trait(以保证不会double freeing)。 官方文档的栈上数据复制部分 中对哪些类型实现了 Copy
trait进行了总结,且对于基本类型来说: iXX
、fXX
、bool
、 char
均实现了 Copy
(且元素均实现 Copy
的元组 ()
也实现 Copy
)。
引用
Rust中的引用类似C/C++中的指针,但使用 &
作为其标记(如 &i32
);在其上调用方法或访问成员不需要使用 ->
而照常使用 .
即可(事实上Rust中没有 ->
这个操作符)。
当然,也可以认为Rust中的引用类似于Java/Python中的引用,只是类型并非原数据类型而是原数据类型的引用类型,且在明确需要原数据类型时需要使用 *
来解引用。但这样需要额外再解释栈对其影响,反而更麻烦。
因为类似指针,所以可以对引用继续进行引用 &&
。不同于C/C++,在使用一个引用类型时,Rust编译器会自动尝试加入足够量的解引用操作,以便操作可以正确执行(显然,该机制只是为方便而存在,不会涵盖更复杂的情况)。
由于Rust变量默认为不可变,在涉及引用时会有更细致的设定: 引用类型的变量 的可变性、 引用本身 的可变性和 该变量所引用变量 的可变性是相互独立的。
let mut a = 1; //a: i32 let b = &a; //b: &i32 *b = 0; //非法:因为b是不可变引用(虽然a本身可变) let mut x = 2; //x: mut i32 let y = &mut x; //y: &mut i32 *y = -1; //合法:由于y是可变引用,所以可以修改 let i = 3; //i: i32 let j = 4; //j: i32 let mut k = &i; //mut k: &i32 k = &j; //合法:因为k本身可变(无关i的可变性) let m = 5; //m: i32 let n = &mut m; //非法:不允许对不可变变量进行可变引用
借用
借用机制通过引用来实现,是对所有权机制的扩充。可以认为Rust中的借用和引用是同一机制的两个名称,分别强调不同的侧面:
- 当谈及所有权时,称之为 借用
- 当谈及数据类型时,称之为 引用
例如:函数参数为 引用 类型这一现象被称为 借用 。
另外,借用/引用还与 寿元 机制相关。
在引用的基本原理之外,借用添加了额外的约束:
- 同一时刻只能有多个不可变借用或一个可变借用
- 借用必须始终有效
这些约束均是为了保证变量和内存安全,避免数据竞争和访问无效内存。
上面引用的各个例子有意避免了违反借用的规则。如下的代码违反借用规则:
let mut a = 1; //x: mut i32 let b = &a; //y: &i32 let c = &a; //c: &i32 let d = &mut a; //非法
可以使用明确的作用域区分来解决该问题:
let mut a = 1; //x: mut i32 { let b = &a; //y: &i32 let c = &a; //c: &i32 } //b和c均已离开作用域,借用消失 let d = &mut a; //合法
您可以在Hypothesis上的該群組內進行評論,或使用下面的Disqus評論。