Rust入门失败之Ownership

Rule No.1

Ownership背后全都围绕这下面这一唯一规则:

Every value has a single owner that determines its lifetime. 每个值只有一个拥有者可以决定它的生命周期.

也就是说: Owner销毁(dropped), 值也销毁. 整个程序的数据像一个Ownership Tree🌲

Copy & Move

Copy types 在赋值、传参和返回时移动(Move)其所有权, 原变量不可引用.

Copy type判断标准:

所有在销毁(dropped)时需要做些特殊处理操作(释放堆上内存或其它类型资源)的类型, 均为不可拷贝.

例如:

  • String
  • Box<T>
  • File 文件类型
  • Mutex<T>

简单数字类型都实现了Copy Trait, 赋值拷贝时直接在栈上生成一份拷贝.

1
2
3
4
5
let str1 = "somnambulance".to_string();
let str2 = str1;

let num1: i32 = 36;
let num2 = num1;

Move Copy 1

  • 对于有指向heap数据的数据类型(包括自定义类型)不允许实现自定义Copy trait.

  • 如果要允许移除(Move)一些被索引的内容, 例如 Vec<String> 可以用Option<T>, 被Move后, 原先的值为None.

1
2
3
let s = vec!["udon".to_string(), "ramen".to_string(), "soba".to_string()];
let t = s;
let u = s; // error[E0382]: use of moved value: `s`

执行上面第一语句后, 内存布局如下:
Move Copy 2

赋值给t后, 内存布局如下:
Move Copy 3

再使用s赋值就报错了, s已经是uninitialized状态.

Box<T>

  • 简单的指向堆栈上数据的指针:let b = Box:new(int);

  • 赋值Move原指针所有权

  • 可以用于递归类型或在编译时期无法计算对象大小的场景

    1
    2
    3
    4
    5
    {
    let point = Box::new((0.625, 0.5)); // point allocated here
    let label = format!("{:?}", point); // label allocated here
    assert_eq!(label, "(0.625, 0.5)");
    } // both dropped here

内存布局如下:
Move Copy 4

Rc and Arc: Shared Ownership

  • 有时候很难满足数据只有一个Owner, 因此引入共享所有权, 允许多个引用.
    变量在栈上实际只是一个指针, 指向堆上一个维护引用计数及存放实际数据类型的内存.
    ⚠️这里并没有违反前面的第一条原则, 因为决定实际数据生命周期的Owner是这个Rc/Arc本身.

  • 当最后一个引用dropped, 整个数据释放.

  • 共享的引用都是immutable, 不可修改共享数据.

  • 循环引用会造成死锁, 这是Rust可能内存泄露的一点. 要避免可以使用 std::rc::Weak

1
2
3
4
5
let s: Rc<String> = Rc::new("shirataki".to_string());
let t: Rc<String> = s.clone();
let u: Rc<String> = s.clone();

s.push_str(" noodles"); // error: cannot borrow immutable borrowed content as mutable

clone()后内存布局如下:
Shared Ownership

二者差异(仅此而已):

  • Rc<T> 非线程安全计数引用
  • Arc<T> 线程安全的计数引用, short for Atomic Reference Count

All images are copyrighted by original authors Jim Blandy & Jason Orendorff who wrote in the book Programming Rust.