Rust八股Memo

Some Rust notes

By Yuchen Jiang

内存管理:所有权&移动

所有权

  1. 丢弃/drop:
    • Rust会自动调用drop函数来释放内存。
    • 变量离开作用域时,Rust会自动调用drop函数来释放内存。
  2. 单一拥有者规则
    • 每个值都有一个所有者
    • 值只能有一个所有者
    • 当所有者离开作用域时,值会被释放
  3. 所有权树:值拥有者及其所拥有的值形成了一棵树
    • 值的拥有者是值的父节点
    • 值拥有的值是子节点
    • 总根都是一个变量,当该变量离开作用域时,树上的所有值都会被释放
    • 单一拥有者规则确保排列不可能比树更复杂
  4. 基于这些严格限制的扩展:
    • 拥有者可以转移:对树形结构进行拆解、重构建
    • 存在基本的Copy类型,类似值拷贝
    • 提供引用计数指针Rc和Arc,实现多拥有者
    • 可以进行借用(borrow),即引用,获得非拥有型指针

移动

  1. 移动(move):“源会把值的所有权转移给目标并变回未初始化状态,改由目标变量来控制值的生命周期。”
    • Rust禁止使用未初始化值
  2. 常见的移动:
    • 从函数返回:Vec::new()返回一个Vec,所有权转移到左值
    • 函数参数传递:fn foo(v: Vec<i32>),所有权转移到函数参数
    • 构造新值:to_string(),所有权转移到新值
  3. 循环获得所有权
    • for循环:for ... in v会将所有权移出v
    • 循环时v会对代码不可见

Copy

  1. Copy类型:实现了Copy trait的类型
    • 整数、浮点、char、bool
    • Copy类型的元组
    • 固定size的数组
    • 任何丢弃值时需要特殊操作的都不是Copy类型
    • String不是Copy类型
    • 默认的Struct和Enum不是Copy类型
    • 通过放置#[derive(Copy, Clone)]来实现Copy trait,对于只拥有Copy类型的字段的Struct和Enum

Rc和Arc

  1. Rc:Reference Counted,引用计数
    • 允许多个所有者
    • 通过引用计数来管理内存
    • 只能在单线程中使用
    • 拥有值不可变
      • 无法使旧值指向新值,因此无法直接构造循环引用
    • Rc::clone()会增加引用计数
    • 自动解引用
  2. Arc:Atomic Reference Counted,原子引用计数
    • 允许多个所有者
    • 线程安全

引用

基本用法

  1. 引用:借用(borrow),即引用,获得非拥有型指针
    • 通过&来创建共享引用,同时拥有任意数量
      • Copy类型
    • 通过&mut来创建可变引用,与其他所有引用互斥
      • 只能有一个可变引用
      • 非Copy类型
      • 在可变引用存在时,即使拥有者也不能使用值
  2. 引用传递性
    •  fn show(table & Table) {
         for (artist, works) in table {
           for work in works {
             //...
           }
         }
       }
      
      
    • 迭代中对HashMap的共享引用就是对key和value的共享引用
    • 迭代中对向量的共享引用就是对向量中每个元素的共享引用
  3. 按值传递和按引用传递
    • 按值传递:将值的所有权转移到函数参数
    • 按引用传递:将值的引用传递到函数参数
      • 需要显示地取引用show(&table)
  4. 引用的基本用法
    • 通过&来创建引用
    • 通过*来显式解引用
    • .按需解引用,可穿透多层引用
      • println!会展开成.运算符代码,自带解引用
      • .也可以隐式借用左操作数的引用,例如.sort()
    • Rust中可以对引用赋新值
    • 比较运算符可以解引用,但需要两端具有相同的类型
      • 使用 std::ptr::eq来比较引用地址
    • 数学运算符可以解引用
    • 引用永不为空
      • 使用Option<&T>来表示可能为空的引用

引用安全

  1. 生命周期
    • Rust通过生命周期来管理引用的有效性
    • 为每个引用分配一个生命周期
    • 生命周期是Rust在编译期虚构的产物,不存在运行期表示
  2. 生命周期约束
    1. 对变量x的引用&x的生命周期不能超过x的生命周期(变量本身必须cover它产生的引用)
    2. 如果将引用存储在变量r中,则引用类型必须在变量r从初始化到最后一次使用的整个生命周期内都可以访问(引用本身必须cover 存储它的变量)
  3. 静态变量:生命周期是全局的
    • 必须初始化
    • 可变静态变量需要使用unsafe关键字,非线程安全
    • 具有'static生命周期
  4. 引用作为参数时的生命周期
    •  fn foo<'a>(x: &'a i32) -> &'a i32 {
         x
       }
      
    • 'a是生命周期参数,表示该函数能接受具有任意生命周期'a的引用
      • 可以是可能的最小生命周期:覆盖foo调用
      • “如果确实看到一个带有 g(p: &i32) 签名的函数(或者带着生命周期写成 g<’a>(p: &’a i32)),那么就可以肯定它没有将其参数 p 藏在任何超出此调用点的地方。”
  5. 将引用传递给函数
    • 在调用时不需要显式指定生命周期参数
    • 编译器会自动确认传递的引用的生命周期满足最大最小条件
      • 最大条件:引用的生命周期不会超过被引用的值的生命周期
      • 最小条件:引用的生命周期可以cover函数的调用,即函数标注的生命周期
  6. 返回引用
    • “当函数以单个引用作为参数并返回单个引用时,Rust 会假定两者具有相同的生命周期”
    • 实际使用中可以省略签名中的生命周期参数
  7. 结构体中的引用
    • 当引用类型出现在另一个类型的定义中时,必须显式指定生命周期参数
    • 例子:
       struct Foo<'a> {
         x: &'a i32,
       }
      
      • 创建的每个Foo实例都会获得一个全新的生命周期’a
      • 存储在x中的引用的生命周期必须cover ‘a
      • ‘a 必须比存储在Foo中的任何内容的生命周期都要长
      • 将新值存储在x中时,存储进的新值会对 ‘a 进行上限的限制
  8. 将具有生命周期参数的类型放置在其他类型中
    • 必须显式指定生命周期参数
    • 比如:
      •   struct D {
            s: S<'static'>
          }
        
          struct D<'a> {
            s: S<'a'>
          }
        
    • “类型的生命周期参数总会揭示它是否包含某些值得关心其生命周期的引用(也就是非 ‘static 的)以及这些生命周期可以是什么。”
      • fn parse_record<'i>(input: &'i [u8]) -> Record<'i> { ... }
      • “不用看 Record 类型的定义就可以知道,如果从 parse_record 接收到 Record,那么它包含的任何引用就必然指向我们传入的输入缓冲区,而不是其他地方(’static 静态值除外)。”
  9. 不同的生命周期参数
    • 通过'a'b来表示不同的生命周期参数
    • 例子:
        fn foo<'a, 'b>(x: &'a i32, y: &'b i32) -> &'a i32 {}
      
      • xy的生命周期不同
      • 放宽限制
  10. 生命周期省略
    • “如果函数的参数只有一个生命周期,那么 Rust 就会假设返回值具有同样的生命周期”
    • “如果函数的参数有多个生命周期,那么就没有理由选择某一个生命周期作为返回值的生命周期,Rust 会要求你明确指定生命周期。”
    • “如果函数是某个类型的方法,并且具有引用类型的 self 参数,那么 Rust 就会假定返回值的生命周期与 self 参数的生命周期相同。”
      • “Rust 假定无论你借用的是什么,本质上都是从 self 借用的”
    • “在最简单的情况下,你可能永远不需要为参数写出生命周期。Rust 会为需要生命周期的每个地方分配不同的生命周期。”

共享和可变

  1. 共享访问是只读
  2. 可变访问是独占
Share: X (Twitter) Facebook LinkedIn