1.9 所有权(简单回顾):所有权的核心思想、如何实现 Copy trait、值的删除(丢弃)、值删除的顺序
这篇文章只对所有权进行简单回顾。
1.9.1. 所有权的核心思想
Rust内存模型的核心思想是所有值都只有一个所有者。也就是说只有一个位置(通常是作用域来) 负责释放每个值。
这种效果是通过借用检查器(详见 1.11.2. 借用检查器(Borrow Checker))实现的,如果值移动了(赋值新变量、推到Vec上、置于堆内存上等),其所有者也变成新的位置了。
所有者实际上就是内存上的一个位置,数据所在的位置就是值的所有者。移动指的是数据从一个位置转移到另一个位置,新的位置就是数据的所有者。
但是有些类型不执行这种规则:如果值的类型实现了Copy trait,那么在重新赋值时不是移动而是复制。也就是把值复制一份放到新的位置。
1.9.2. 如何实现Copy trait
实现Copy trait的类型必须可以按位(bit)来复制值。
能实现Copy trait的类型自然不包括:
- 含有
non-copy类型的类型 - 如果一个类型在其值被丢弃时,必须执行某些特殊的资源释放操作
为什么呢?
想象一下,如果Box<T>实现了Copy trait,进行赋值:box1 = box2,这时候这两个变量都认为自己在堆内存上有一块专属于自己的空间,所以当它们走出作用域时,它们都会尝试释放那块内存,导致双重释放(double free),其危害在 【Rust指南】4.2. 所有权规则、内存与分配 中已作介绍,可以点击链接查看,这里不做重复。
1.9.3. 值的删除(丢弃)
当值不再被需要时,其所有者会将其删除。
值的删除(或者叫丢弃)发生于值走出作用域时。类型会递归地将其所包含的值删除。比如说我们要删除一个复杂类型的变量,会导致需要删除很多值。
但Rust不会发生多次删除同一个值的情况(因为所有权设计)。变量若含有对其他值的引用(不拥有该值),当变量被删除时,其它的值不会被删除。
这样说可能难理解,那我们看一个简单的例子:
fn main() {
let x1 = 42;
let y1 = Box::new(x1);
{
let z = (x1, y1);
}
let x2 = x1;
}
x1是i32类型,y1是Box<i32>类型,它拥有堆上的一块分配,里面存的是x1值的副本(因为i32实现了Copy,Box::new(x1)并不会借用x1)- 使用
{}创建了一个新的作用域,在这个小作用域里创建了z z是元组类型,其值是(x1, y1),x1是i32类型,实现了Copytrait,所以x1是把自己的值复制了一份给z;y1是Box<i32>,没有实现Copytrait,所以它不能复制值,而是把所有权转移给了z- 离开了小作用域之后又使用了一次
x1,x1此时还保持有效,因为x1是把自己的值复制了一份给z,自身仍然保持有效。 - 而
y1在给z赋值之后就失效了,因为它把所有权转移给了z
1.9.4. 值删除的顺序
- 变量(包括函数的参数)按照相反的顺序进行删除。
- 嵌套的值按照源代码的顺序进行删除。在上面的例子中,当离开内部作用域时会删除
z:它先删除第一个元素(那个被复制的i32),再删除第二个元素(那个Box)。因为y1的所有权已经移动进z,之后不会再单独删除y1。
注意:Rust暂时不允许在单个值内进行自我引用