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. 所有权应注意的问题

  1. 所有权只会发生在堆上分配的数据,对比 C++,可以说所有权只会发生在指针上。
  2. 基本类型的存储都在栈上,因此没有所有权的概念。

下一章:Rust 借用所有权

Rust 借用所有(Borrowing):Rust 语言中,在 堆(heap)上分配的变量都有所有权(wnership)。其实,Rust 提供了更好的解决方案,它支持对所有权的 出借 borrowing< 。当把一个具有所有权的变量传递给函数时,就是把所有权借用给函数的参数,当函数返回后则自动收回所有权。本章内容包括: 什么是借用 Borrowing 和 可变引用。