发生所有权转移的几种情况

Rust中的所有权转移比较复杂,官方并没有很明确地解释哪些情况下会发生所有权转移。但所有权转移问题是必须要理解的,它贯穿整个Rust。下面是一些个人理解和总结。

可以将Rust中的所有权转移问题用其他语言的按值拷贝和按引用拷贝来理解(其实,Rust的Copy和Move是特殊的值拷贝、引用拷贝):

  • 需要一个值时,是按值拷贝,该值会被绑定到目标变量

    • 注意,被转移的值不一定是原始值,比如是克隆或拷贝后形成的新值,此时原始值的所有权不会被转移

    • 如果这个值的类型可Copy或该值被克隆,则绑定到目标变量的是Copy后的新值

    • 如果这个值的类型是不可Copy的,则该值的所有权被转移

      
      #![allow(unused)]
      fn main() {
      let a = &"hello".to_string();
      // b需要值,拷贝给b的值不是a指向的值,而是它的克隆值
      // 因此,a仍然可用
      let b = (*a).clone();
      }
      
  • 需要一个引用时,是按引用拷贝,该值的所有权不会被转移

    • 引用类型都是可Copy的
    • 从更本质的角度来看待,为某个值创建引用时,该值的所有权不会发生转移(如&(*refA)不会转移refA所指向数据的所有权)

总结来说,有以下几种上下文可能会发生值的所有权转移。之所以是可能转移所有权,是因为这些上下文本身不会转移所有权,而是这些上下文中可能出现了需要值的地方,转移所有权总是发生在需要值且数据类型不可Copy的地方

  • 将变量赋值给let声明的新变量
  • 进入新的词法作用域,包括:
    • 大括号、match、if、for in、loop、while、if let、while let、函数、闭包等
    • 需注意,上面这些结构都有自己的作用域,在作用域内会发生所有权转移
    • 这些解结构中有些有条件表达式(如if/while),在条件表达式中很可能也会出现所有权转移的情况
  • 某些情况下解引用变量时(解引用操作本身不会转移所有权,而是在使用解引用后的数据时很可能会发生所有权转移)
  • 其他需要值的时候(比如大小比较时两端都需要值,比如as进行类型转换时也需要值)

下面给一些简单的示例来说明。

let声明变量(绑定值)时,会转移所有权:

fn main(){
  let a = "hello".to_string();
  let b = a;  // a所绑定值的所有权被转移给b
  // println!("{}", a);
}

大括号创建的作用域内使用变量,会转移所有权:

fn main(){
  let a = "hello".to_string();
  {
    a;    // 大括号作用域内使用a,a所绑定的值的所有权被转移
          // 不要使用println!("{}", a);来测试,println!取的是a的引用
  }
  println!("{}", a);
}

if/for/while/loop等创建的作用域内使用变量,会转移值的所有权:

fn main(){
  let a = vec![11,22,33];
  let b = "hello".to_string();
  for i in a {  // a的所有权在调用into_iter的时候被转移
    // println!("{}", a);  // 作用域内不可使用a
    b;   // 作用域内使用b,b的所有权被转移
  }
  // println!("{}", b);
  
  let c = 33;
  if(c > 32){  // 比较操作需要值,因此会发生所有权转移,但33可Copy
               // Copy后的值将在跳出if语句块作用域时被销毁
    ...
  }
}

解引用时:

fn main(){
  let a = &"hello".to_string();
  let b = *a;   // b需要值,解引用*a会转移a所指向数据的所有权给b
  // println("{}", a);
  
  let a = &"hello".to_string();
  let b = &*a;  // b需要引用而非需要值,并且在创建a所指向数据的引用时不会转移所有权
}

函数参数:

// foo参数需要值,换句话说,需要按值拷贝该参数对应的数据
fn foo(var: String) -> String {
  ...
}

fn main(){
  let a = "hello".to_string();
  foo(a);   // a的所有权被转移
  // println!("{}", a);
}