所有权系统和引用在Rust中是至关重要的,它的设计使得rust的GC机制与众不同,也让rust获得了性能上的回报。

所有权

  • 所有权:一个数值只能被一个变量所拥有,即该变量唯一拥有该数值,若变量离开了其作用域,数值也会被释放掉。
  • 如果将一个变量赋值给另一个变量,则意味着对应的值的所有权会被移交出去,那么原先的变量将不再拥有值的所有权,这也是为什么在rust中,变量的赋值也叫变量的绑定
// 例子
fn main(){
  let s1 = String::from("Hello");
  let s2 = s1; // 所有权移交 -> 即move
  println!("{}", s1); // 报错
}
  • 可以看到,上述例子的s1将所有权移交给s2后,自己便不再拥有对字符串Hello的控制。

从内存的视角观察

  • 还是上述例子,在内存里表示出来,如下图所示。

rust图示

  • 可以看出,再进行变量绑定时,s2实际上是s1的浅拷贝结果,但唯一不同的是指针的移交操作,即指针不是单纯的拷贝,而是从s1上“移交”给了s2,这也是一个move的过程。

  • 如果不想让s1的所有权移交出去,则可以调用String的clone方法来实现深拷贝 ,在堆内存上再开辟一个空间,这样两个变量都有对应的值,这样就不会发生变量无法使用的情况了。

let s1 = String::from("hello");
let s2 = s1.clone(); // 直接重新开辟一个空间,尽量少用,会影响rust性能

println!("s1 = {}, s2 = {}", s1, s2);
  • 需要注意的是,这并不能说明存储在堆内存中的数据只能有一个引用(即指针),这里只强调了move这个操作,即所有权的移交过程,而没有说明堆数据只能有一个引用。

Copy类型

  • 之所以提到copy类型是因为它与上述的String类型有所不同,具有Copy属性的数据类型的数据都是直接存储于栈内存中的,即具有copy特征的数据在进行赋值时,旧变量依然可以使用
fn main(){
  let a = 1;
  let b = a; // 将 a 赋值给 b
}
  • 当赋值时,进行了浅拷贝操作,由于简单类型直接在栈上进行浅拷贝,开销小,两个1时存储在不同的栈内存空间里,所以两个变量都有与之对应的数值,所以不会像刚才的String类型一样,发生s1不可使用的情况。

  • 下面是一些具有Copy属性的类型:

    • 整数类型
    • 布尔类型
    • 浮点数类型
    • 字符类型
    • 元祖
    • 不可变引用 &T

好处: 避免内存二次释放以及开发上不必要的困扰

  • Rust垃圾回收比较特殊,它是在每个作用域结束时会调用一次drop函数来清理对内存。拿上述的String的例子来说,两个String变量指向同一个位置,如果没有所有权的限制,那么s1和s2离开作用域时,gc会对同一块堆内存进行二次释放,这样会导致内存的泄漏问题。
  • 所以一旦有了所有权的移交,Rust认为s1已经不再拥有对数据的所有权,所以不会对s1处理任何东西,而只对s2的堆内存进行释放处理。
  • 同时,所有权还能保证开发时产生不必要的困扰,假如有多个变量对同一块内存进行引用,那么当其中一个变量一不小心对内存进行修改时,其产生的副作用就会波及到所有与之相关的变量上,造成令人匪夷所思的结果。显然,有了所有权的限制,我们不会有上述的担忧。