07|所有权:值的生杀大权到底在谁手上?

73 阅读4分钟

正式开始

变量在函数调用时发生了什么

main() 函数中定义了一个动态数组 data 和一个值 v,然后将其传递给函数 find_pos,在 data 中查找 v 是否存在,存在则返回 v 在 data 中的下标,不存在返回 None

fn main() {
    let data = vec![10, 42, 9, 8];
    let v = 42;
    if let Some(pos) = find_pos(data, v) {
        println!("Found {} at {}", v, pos);
    }
}

fn find_pos(data: Vec<u32>, v: u32) -> Option<usize> {
    for (pos, item) in data.iter().enumerate() {
        if *item == v {
            return Some(pos);
        }
    }
    
    None
}

动态数组因为大小在编译期无法确定,所以放在堆上,并且在栈上有一个包含了长度和容量的胖指针指向堆上的内存

大多数语言多重引用给内存管理带来极大麻烦

大多数语言方案

C/C++

开发者手工处理,容易出现内存泄漏和野指针问题

Java

使用追踪式GC,通过定期扫描堆上数据看还有没有人引用,来管理堆内存

存在STW(Stop The World)的问题

OC/Swift

使用自动引用计数(ARC),在编译时自动添加维护引用计数的代码,引用计数为0时清理内存。

存在性能问题和循环引用问题

Rust解决思路

所有权和 Move 语义

通过制定规则保证一个值最好只有一个拥有者(单一所有权)

  1. 一个值只能被一个变量所拥有,这个变量被称为所有者
  2. 一个值同一时刻只能有一个所有者(不能有两个变量拥有相同的值。变量赋值、参数传递、函数返回等行为,旧的所有者会把值的所有权转移给新的所有者,以便保证单一所有者的约束)
  3. 当所有者离开作用域,其拥有的值被丢弃,内存得到释放

作用域:在 Rust 中,一对花括号括起来的代码区就是一个作用域

举个例子

fn main() {
    // 创建了一个不可变数据 data
    let data = vec![1, 2, 3, 4];
    // 将 data 赋值给 data1
    // 赋值之后,data 指向的值被移动给了 data1,它自己便不可访问了
    // 这里的解决方式,可以将data.clone()在堆上复制一份传给data1
    let data1 = data;
    // data1 作为参数被传给函数 sum(),在 main() 函数下,data1 也不可访问了
    println!("sum of data1: {}", sum(data1));
    // 试图访问 data1 和 data,有两个错误
    // value borrowed here after move
    println!("data1: {:?}", data1);
    // value used here after move
    println!("sum of data: {}", sum(data));
}

fn sum(data: Vec<u32>) -> u32 {
    data.iter().fold(0, |acc, x| acc + x)
}

解决栈上简单数据的所有权转移问题

  1. 如果你不希望值的所有权被转移,在 Move 语义外,Rust 提供了 Copy 语义。如果一个数据结构实现了 Copy trait,那么它就会使用 Copy 语义。在你赋值或者传参时,值会自动按位拷贝(浅拷贝)
  2. 如果你不希望值的所有权被转移,又无法使用 Copy 语义,那你可以借用数据

Rust默认是Move语义,Copy语义可选需要自己实现

Copy 语义和 Copy trait
  1. 符合 Copy 语义的类型,在你赋值或者传参时,值会自动按位拷贝
  2. 默认使用Move,如果实现了Copy则使用Copy(当你要移动一个值,如果值的类型实现了 Copy trait,就会自动使用 Copy 语义进行拷贝,否则使用 Move 语义进行移动
  3. 可以使用rustc --explain EXXXX 查看具体错误和错误样例

快速验证数据结构是否实现了Copy trait

  1. 原生类型,包括函数、不可变引用和裸指针实现了 Copy;
  2. 数组和元组,如果其内部的数据结构实现了 Copy,那么它们也实现了 Copy;
  3. 可变引用没有实现 Copy;
  4. 非固定大小的数据结构,没有实现 Copy。

可以查看官方Copy trait文档的Implementors

小结

  1. 所有权:一个值只能被一个变量所拥有,且同一时刻只能有一个所有者,当所有者离开作用域,其拥有的值被丢弃,内存得到释放。
  2. Move 语义:赋值或者传参会导致值 Move,所有权被转移,一旦所有权转移,之前的变量就不能访问。
  3. Copy 语义:如果值实现了 Copy trait,那么赋值或传参会使用 Copy 语义,相应的值会被按位拷贝(浅拷贝),产生新的值

image.png