Rust - 所有权

141 阅读4分钟

作用: 管理堆数据的存储和访问.

设计原则:
以提升运行时的性能为主;
在进行赋值操作时进行所有权转移;

目的: 解决内存安全

规则

  1. Rust 中每个值都有一个被其称为所有者 owner 的变量
  2. 值在任何时刻都只有一个所有者
  3. 当所有者离开作用域时, 值将被丢弃

内存

常见的内存管理方式有三种

  • 垃圾回收机制: JS

  • 手动分配和释放内存

  • 通过所有权系统管理内存

    所有权是以编译时的内存安全检查替代运行时的垃圾回收机制。

堆与栈

栈:

  • 后进先出, Last In Fast Out.
  • 栈中的数据具有已知的固定大小
  • 入栈总是在栈顶

堆:

  • 缺乏组织, 存储相对混乱
  • 需要返回表示该内存地址的指针 pointer
  • 需要将指针存储在栈中, 通过指针访问数据

堆中的数据存储和访问, 要比栈中的复杂太多, 需要考虑到位置, 内存空间等一系列问题.

实现

Rust 通过借用检查器(一种静态分析器)实现所有权。

借用检查器是 Rust 编译器的一个组件,它跟踪整个程序中数据的使用位置,并且根据所有权规则,它能够识别需要执行释放的数据位置。

借用检查器可以确保在运行时,已释放的内存永远不会被访问。它甚至消除了数据竞争问题。

rust 面向编译器编程。

变量作用域

变量在离开作用域的时, 会调用 Drop Trait 用于释放内存.

变量与数据的交互

move: 移动后原变量无效

针对堆内存, 会导致所有权转移.

let s1 = String::from("hello");
let s2 = s1;

print!("s1: {}", s1); // 错误, s1 不可访问

需满足规则: 值在任何时刻都只有一个所有者

原理: 如果原变量有效, 就会存在 s1 和 s2 的两个数据指针同时指向了同一位置, 在离开作用域, 会释放两次, 导致二次释放的错误.

**隐藏的设计原则: ** Rust 永远不会自动创建的深拷贝. 一切以提高运行时的性能为主.

clone:

在实际开发中, 如果确实需要深度复制 String 中堆上的数据, 可以使用 clone.

只会复制数据, 不会同步更改.

let s1 = String::from('Hello')'
let s2 = s1.clone();

println!("s1 = {}, s2={}", s1, s2);

copy:

对于栈中的数据, 变量的赋值可以通过拷贝实现, 复制特定的语义.

let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);

对于用了 Copy Trait 的特殊注解, 都可以实现旧的变量在赋值给其它变量后继续使用.

但是禁止实现了 Drop Trait 的类型使用 Copy Trait.

其它

函数和返回值也可以进行所有权的转移, 在语义上与变量赋值相似.

引用与借用

借用 borrowing 是一种在不获取所有权的情况下访问数据

& 引用 reference 在捕获去所有权的情况下访问数据; 返回的是一个内存地址, 不可能为空. 是一种指针类型

* 解引用(dereferencing) 是与引用相反的操作; 可以获取指向内存中的数据. 当需要数据的时候就需要解引用。

特殊的作用域

引用的作用域是从声明的位置开始一致持续到最后一次使用位置.

编译器在作用域结束之前判断不再使用引用的能力被称为 非词法作用域生命周期.

// 不可运行
{
    let mut s = String::from("hello");

    let r1 = &s; // 没问题
    let r2 = &s; // 没问题
    let r3 = &mut s; // 大问题

    println!("{}, {}, and {}", r1, r2, r3);
}

// 可运行
{
    let mut s = String::from("hello");

    let r1 = &s; // 没问题
    let r2 = &s; // 没问题
    println!("{} and {}", r1, r2);
    // 此位置之后 r1 和 r2 不再使用

    let r3 = &mut s; // 没问题
    println!("{}", r3);
}

避免数据竞争

当多个引用指向同一个内存位时, 至少有一个在执行写操作, 且它们的动作没有同步时

可变引用: &mut

底层逻辑: 不能在拥有不可变引用的同时拥有可变引用

悬垂引用

指向已分配或已释放内存位置的指针, 及指针无效的情况

解决方案:

  • 在任意给定的时间内,要么只能有一个可变引用, 要么只能有多个不可变引用
  • 引用必须总是有效的