Rust的所有权是保证了Rust程序能够安全运行的重要机制之一。所有权就是变量拥有资源。所有者可以转移变量的所有权;所有权也可一通过引用来共享,引用机制采用多读者,单写者的方式。与所有权机制紧密关联的还有生存期,相当于变量的作用域,在生存期内变量存活,生存期外,变量自动被销毁,内存被回收。这也是为什么Rust没有垃圾回收器,却能实现类似功能的原因。上面提到的生存期,引用和垃圾回收机制都与所有权机制密切相关。
那Rust中的所有权机制是怎样的,先看一个例子:
fn main() {
let s = "hello world".to_owned();
let take_ownship = s;
println!("{}", take_ownship);
}
上面就是一个所有权转移的例子。变量s一开始拥有所有权,后面将变量s赋值给变量take_ownship,此时就进行了所有权转移。变量s变成非初始化,即不能够在转移所有权后使用变量s进行任何操作,除非对变量s进行再赋值。使用其他编程语言编码时,也经常对变量的所有权进行转移来保证数据安全,所有权转移机制其实一点都不陌生。
与引用的关系
引用通过指向变量实体来获取和修改变量实体的内容。由于Rust要求数据是安全的的,即没有数据竟争和空指针等其他安全问题。而引用包括可变引用和不可变引用,所以数据可能存在安全风险。因此,Rust要求使用引用时遵循多读者,单写者的规则,保证数据不会竞争。
多读者就是可以有多个不可变引用:
fn main() {
let s = "hello".to_owned();
let s1 = &s;
let s2 = &s;
println!("s1 = {}, s2 = {}", s1, s2);
}
单写者就是只能有一个可变引用,不能有其他不可变引用或可变引用:
fn main() {
let mut s = "hello".to_owned();
let s1 = &s;
let s2 = &mut s;
s2.push_str(" world");
println!("s1 = {}, s2 = {}", s1, s2);
}
上面的代码会编译报错:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> a.rs:4:14
|
3 | let s1 = &s;
| -- immutable borrow occurs here
4 | let s2 = &mut s;
| ^^^^^^ mutable borrow occurs here
5 | s2.push_str(" world");
6 | println!("s1 = {}, s2 = {}", s1, s2);
| -- immutable borrow later used here
error: aborting due to previous error
因为上面同时存在不变引用和可变引用,违反了单写者规则。正确的做法是:
fn main() {
let mut s = "hello".to_owned();
let s1 = &s;
println!("s1 = {}", s1);
let s2 = &mut s;
s2.push_str(" world");
println!("s2 = {}", s2);
}
上面讲了可变引用与不可变引用的关系。下面讲一下所有权与引用的关系:
引用是依附于所有权上。即,引用的生存期不能超过所有权实体的生存期。引用无法转移所有权。
与垃圾回收机制的关系
Rust的垃圾回收机制与Go/Java的自动垃圾回收机制完全不同,倒有点像C++的RAII机制。Rust的垃圾回收机制依赖于变量的生存期,生存期到了,变量自动被销毁(调用变量关联的drop trait)。而变量的生存期与变量的作用域和所有权转移密切相关。
-
如果一个变量离开
作用域,变量的生存期就到了。但如果变量将所有权转移到另外一个变量(作用域范围更大)则资源的生存期得以“延续”。 -
如果将一个变量的
所有权转移到容器内,如:vector,map等。资源的生存期跟随容器的生存期。 -
如果变量实现了
Clone和Copytrait,则说明该变量的资源是可复制的。Rust并不会使用所有权转移机制,而是直接采用赋值方式。这是所有权转移机制的例外。为什么会有这个例外呢?因为赋值的成本很低,编码上也更方便。下面看一个例子:#[derive(Copy, Clone, Debug)] struct Num { a: u32, b: u32, } fn main() { let n1 = Num { a: 1, b: 2 }; let n2 = n1; println!("n = {:?}, n2 = {:?}", n1, n2); }上面的代码如果使用
所有权转移机制,编译会报错,但实际上能够正常编译运行。在Rust中integer,boolean,char等基础类型都实现了Clone和Copytrait。