Rust 通过单一所有权来限制任意引用的行为

Why


通常的编程语言为了管理内存 通常会采用三种方案:

  1. GC。在运行时不断扫描堆内存,看值是否还存在引用,如果不存在则释放内存。会造成性能的消耗
  2. 引用计数。在编译时自动添加维护引用计数的代码,减轻开发者维护堆内存的负担。但同样地,它也会有不小的运行时性能损耗
  3. 手动管理内存(C++)。通过业内的一些最佳实践来进行内存的管理,但是很鸡肋

以上方案都是通过管理引用的角度去处理的,但是它仍然解决不了根本问题—变量被无限制、有意无意的引用,导致难以管理。

那么如果我们从引用本身出发去处理,对引用本身做一些限制,不让开发者随意去引用呢?
那么我们应该加一些什么限制才可以做到清晰有效的引用呢?

Rust给了我们一种答案,那就是所有权

What


所有权其实就是对堆内存上的引用进行有效管理的一种手段。
所有权决定了值的生杀大权,避免了堆内存上变量的多重引用问题

所有权规则:

  1. 一个值只能被一个变量拥有,这个变量被称为所有者
  2. 一个值同一时刻只能有一个所有者,也就是说同一时刻不能有两个变量拥有同一个值
  3. 当所有者离开作用域时,其拥有的值会被抛弃,内存得到释放

Move语义


Move 语义:赋值或者传参会导致值 Move,所有权被转移,一旦所有权转移,之前的变量就不能访问

  1. fn main() {
  2. let data = vec![1, 2, 3, 4];
  3. // 这里所有者已经转移,data内存释放,后续无法访问
  4. let data1 = data;
  5. // 调用sum函数时,data1的所有权也已经转移给了参数,data1后续也无法访问
  6. println!("sum of data1: {}", sum(data1));
  7. // 所有权已经转移所以报错
  8. println!("data1: {:?}", data1); // error1
  9. println!("sum of data: {}", sum(data)); // error2
  10. }
  11. fn sum(data: Vec) -> u32 {
  12. data.iter().fold(0, |acc, x| acc + x)
  13. }

那么如果我们在赋值或者传参数时,不想让所有权转移呢?也就是说我们仍然想访问原有的变量呢?

  1. 使用Clone方法,将数据克隆出一份副本let data1 = data.clone();
  2. 如果如果你不希望值的所有权被转移,在 Move 语义外,Rust 提供了 Copy 语义。如果一个数据结构实现了 Copy trait,那么它就会使用 Copy 语义。这样,在你赋值或者传参时,值会自动按位拷贝(浅拷贝)。
  3. 使用借用(&)。let data1 = &data;

当你在赋值或者传参的时候,默认会进行Move,但是如果该值的类型实现了Copy Trait,那么就会将值按位拷贝,而不是进行Move

实现了Copy Trait的类型

官方提供的实现了Copy trait的类型

fn is_copy<T: Copy>() {}

fn types_impl_copy_trait() {
    is_copy::();
    is_copy::();
    // all iXX and uXX, usize/isize, fXX implement Copy trait
    is_copy::<i8>();
    is_copy::<u64>();
    is_copy::<i64>();
    is_copy::<usize>();

    // function (actually a pointer) is Copy
    is_copy::<fn()>();

    // raw pointer is Copy
    is_copy::<*const String>();
    is_copy::<*mut String>();

    // immutable reference is Copy
    is_copy::<&[Vec<u8>]>();
    is_copy::<&String>();

    // array/tuple with values which is Copy is Copy
    is_copy::<[u8; 4]>();
    is_copy::<(&str, &str)>();
}

fn types_not_impl_copy_trait() {
    // unsized or dynamic sized type is not Copy
    is_copy::();
    is_copy::<[u8]>();
    is_copy::<Vec>();
    is_copy::();
    // mutable reference is not Copy
    is_copy::<&mut String>();

    // array / tuple with values that not Copy is not Copy
    is_copy::<[Vec<u8>; 4]>();
    is_copy::<(String, u32)>();
}

fn main() {
    types_impl_copy_trait();
    types_not_impl_copy_trait();
}

借用

  • 只读借用都实现了Copy trait。也就是在传参、赋值和返回值时,只要使用了&就会产生浅拷贝
  • 一个值可以有唯一一个活跃的可变引用。可变引用(写)和只读引用(读)是互斥的关系,就像并发下数据的读写互斥那样,不能同时存在

    总结

    image.png