Rust 所有权(ownership)
Rust 语言中每一个值都有一个对应的变量,这个变量就成为这个值的 所有者。从某些方面说,定义一个变量就是为这个变量和它存储的数据定义一种所有者管理,声明这个值由这个变量所有。
例如,对于 let age = 30 这条语句,相当于声明 30 这个值由变量 age 所有。
任何东西只有一个所有者,Rust 中是不允许有共同所有者这个概念的。
1. 所有权规则
- Rust 中的每个值都有一个变量,称为其所有者。
- Rust 中,任何特定时刻,一个数据只能有一个所有者。
- Rust 中,不允许两个变量同时指向同一块内存区域。变量必须指向不同的内存区域。
- 当所有者不在程序运行范围时,该值将被删除。
2. 转让所有权
既然所有权就是一个东西属不属于你,你有没有权力随意处理它,比如送人,比如扔掉。那么转让所有权就会时不时的发生。
Rust 语言中转让所有权的方式有以下几种:
- 把一个变量赋值给另一个变量。
- 把变量传递给函数作为参数。
- 函数中返回一个变量作为返回值。
接下来我们分别对这三种方式做详细的介绍
1)把一个变量赋值给另一个变量
Rust 自己宣称的最大卖点是它的 内存安全,这也是它认为能够取代 C++ 作为系统级别语言的自信之一。
Rust 为了实现内存安全,Rust 严格控制谁可以使用内存和什么时候应该限制使用内存。
说起来有点晦涩难懂,我们直接看代码,然后通过代码来解释
fn main(){ // 向量 v 拥有堆上数据的所有权 // 每次只能有一个变量对堆上的数据拥有所有权 let v = vec![1,2,3]; // 赋值会导致两个变量都对同一个数据拥有所有权 // 因为两个变量指向了相同的内存块 let v2 = v; // Rust 会检查两个变量是否同时拥有堆上内存块的所有权。 // 如果发生所有权竞争,它会自动将所有权判给给新的变量 // 运行出错,因为 v 不再拥有数据的所有权 println!("{:?}",v); }
上面的代码中我们首先声明了一个向量 v。所有权的概念是只有一个变量绑定到资源,v 绑定到资源或 v2 绑定到资源。
上面的代码会发生编译错误 use of moved value: v。这是因为赋值操作会将资源的所有权转移到了 v2。这意味着所有权从 v 移至 v2 ( v2 = v ),移动后 v 就会变得无效。
2)把变量传递给函数作为参数
将堆中的对象传递给闭包或函数时,值的所有权也会发生变更
fn main(){ let v = vec![1,2,3]; // 向量 v 拥有堆上数据的所有权 let v2 = v; // 向量 v 将所有权转让给 v2 display(v2); // v2 将所有权转让给函数参数 v ,v2 将变得不可用 println!("In main {:?}",v2); // v2 变得不可用 } fn display(v:Vec<i32>){ println!("inside display {:?}",v); }
编译运行以上 Rust 代码,报错如下
error[E0382]: borrow of moved value: `v2` --> src/main.rs:5:28 | 3 | let v2 = v; // 向量 v 将所有权转让给 v2 | -- move occurs because `v2` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait 4 | display(v2); | -- value moved here 5 | println!("In main {:?}",v2); | ^^ value borrowed here after move
修复的关键,就是注释掉最后的输出 v2
fn main(){ let v = vec![1,2,3]; // 向量 v 拥有堆上数据的所有权 let v2 = v; // 向量 v 将所有权转让给 v2 display(v2); // v2 将所有权转让给函数参数 v ,v2 将变得不可用 //println!("In main {:?}",v2); // v2 变得不可用 } fn display(v:Vec<i32>){ println!("inside display {:?}",v); }
编译运行以上 Rust 代码,报错如下
inside display [1, 2, 3]
3)函数中返回一个变量作为返回值
传递给函数的所有权将在函数执行完成时失效。
也就是函数的形参获得的所有权将在离开函数后就失效了。失效了数据就再也访问不到的了。
为了解决所有权失效的问题,我们可以让函数将拥有的对象返回给调用者。
fn main(){ let v = vec![1,2,3]; // 向量 v 拥有堆上数据的所有权 let v2 = v; // 向量 v 将所有权转让给 v2 let v2_return = display(v2); println!("In main {:?}",v2_return); } fn display(v:Vec<i32>)-> Vec<i32> { // 返回同一个向量 println!("inside display {:?}",v); return v; }
编译运行上面的 Rust 代码,输出结果如下
inside display [1, 2, 3] In main [1, 2, 3]
3. 所有权和基本数据类型
所有的基本数据类型,把一个变量赋值给另一个变量,并不是所有权转让,而是把数据复制给另一个对象。简单的说,就是在内存上重新开辟一个区域,存储复制来的数据,然后把新的变量指向它。
这样做的原因,是因为原始数据类型并不需要占用那么大的内存。
fn main(){ let u1 = 10; let u2 = u1; // u1 只是将数据拷贝给 u2 println!("u1 = {}",u1); }
编译运行以上 Rust 代码,输出结果如下
u1 = 10
4. 所有权应注意的问题
- 所有权只会发生在堆上分配的数据,对比 C++,可以说所有权只会发生在指针上。
- 基本类型的存储都在栈上,因此没有所有权的概念。
下一章:Rust 借用所有权
Rust 借用所有(Borrowing):Rust 语言中,在 堆(heap)上分配的变量都有所有权(wnership)。其实,Rust 提供了更好的解决方案,它支持对所有权的 出借 borrowing< 。当把一个具有所有权的变量传递给函数时,就是把所有权借用给函数的参数,当函数返回后则自动收回所有权。本章内容包括: 什么是借用 Borrowing 和 可变引用。