Rust 所有权及借用规则

  • 单一所有权
    • Rust 明确了所有权的概念,所有权就是拥有资源的管理权利
    • 在 Rust 中,值也是一种资源,每一个值都有一个被称为其所有者(owner)的变量;值在任何时候都只能有一个所有者;当所有者(变量)离开作用域,这个值将被丢弃(drop)
    • Rust 通过单一所有权来限制任意引用的行为
  • 赋值和参数传递通常是编程语言不可或缺的组成部分,Rust 对其定义有三种语义行为
    • 移动语义(move):即所有权转移,将值的所有权转移给新的所有者
    • 复制语义(copy)
    • 借用语义(borrow)
      • 在 Rust 中,“借用”本质上就是“引用”,只不过多了些规则,便提出了新概念“借用”
      • 创建一个引用的行为称为借用
        • &T:不可变借用
        • &mut T: 可变借用
      • 借用规则
        • 可存在多个不可变借用,但只能有一个可变借用
        • 可变借用与不可变借用的作用域不能交叠,可变借用之间的作用域也不能交叠
        • 借用只在所有权者的生命周期内才有效,防止悬空引用
        • 所有权者不能在借用期内修改值或者转移
  • 生命周期注释

Clone Trait vs Copy Trait

  • Clone traitCopy trait 的 supertait(基类)
  • 无论是 Clone 还是 Copy,目的都是为了拷贝值
    • Copy 是隐式复制行为,由编译器自动执行的;它是一个标记特征,告诉编译器表明该类型可以安全、简单得直接按位拷贝
      • 按位拷贝:等同于 C 语言里的 memcpy:C 语言中的 memcpy 会从源所指的内存地址的起始位置开始拷贝 n 个字节,直到目标所指的内存地址的结束位置。但如果要拷贝的数据中包含指针,该函数并不会连同指针指向的数据一起拷贝
      • 如何实现 Copy 特征
        struct MyStruct;
        
        impl Copy for MyStruct { }
        
        impl Clone for MyStruct {
            fn clone(&self) -> MyStruct {
                *self // 直接解引用到原值
            }
        }
        
      • 在 Rust 中,什么数据结构实现了 Copy trait 呢?
        • Rust 标准库中实现 Copy trait 的所有数据结构
        • 大概心智模型
          • 固定大小的数据类型,如标量类型、共享只读引用、函数指针等
          • 无法安全复制的类型不能实现 Copy trait
            • 可变引用类型
            • 任何类型的实现 Drop trait,防止双重释放
          • 需要所有成员都是 Copy trait 的,结构体才能实现 Copy trait
    • Clone 则是开发者通过 clone 方法显示调用的
      • Copy 无法重载,而 Clone trait 则可以重载
      • Copy trait 和 Clone trait 本质上都是按位复制,但 Clone trait 可能会涉及到更多的内存分配和操作,而 Copy trait 则只是简单地复制值,Clone trait 更多期望实现真正克隆语义。故在 Rust 中,Copy 的特征充当于“浅”克隆,而 Clone 则相当于“深”拷贝
      • Rust 标准库中实现 Clone trait 的所有数据结构

生命周期

  • 每个引用都有自己的生命周期,一旦超过其生命周期,其拥有的值被丢弃
    // 当程序离开 "hello" 所有者变量 s 所在生命周期(作用域),其拥有的值被丢弃。
    fn main() {
        let s_ref;                                   ——————————————————————                      
        {                                                                 |
            let s = String::from("hello");   ----------                                                  |                                                  s 生命周期范围        s_ref 生命周期范围
            s_ref = &s;                      _________|    }                                                                 | 
        println!("{}", s_ref);                       ——————————————————————
    }
    
  • Rust 编译器有一个借用检查器,通过比较生命周期来确保所有的借用都是有效的,确保了引用的有效性,避免了悬垂引用
  • 在单个函数上文内,Rust 可以通过作用域推导借用的生命周期,但不同函数上下文之间的返回借用则需要泛型生命周期标注
    • Rust 无法推导动态逻辑下返回的借用的生命周期
      fn longest(x: &str, y: &str) -> &str {
          if x.len() > y.len() {
              x
          } else {
              y
          }
      }
      
    • 返回的借用需要标注与入参的生命周期关联
      // 表示返回的引用和这两个参数至少存活的一样久
      fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
          if x.len() > y.len() {
              x
          } else {
              y
          }
      }
      
    • 如果返回的引用没有指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值。然而它将会是一个悬垂引用,因为它将会在函数结束时离开作用域
      fn longest<'a>(x: &str, y: &str) -> &'a String {
          let s = String::from("hello");
          &s // error
      }
      
  • 结构体定义中的生命周期注解
    • 在 Rust 中结构体定义中的每一个引用需要添加生命周期注解
      struct ImportantExcerpt<'a> {
        part: &'a str,
      }
      
    • 方法定义中的生命周期注解
      impl<'a> ImportantExcerpt<'a> {
        fn announce_and_return_part(&self, announcement: &str) -> &'a str {
            println!("Attention please: {}", announcement);
            self.part
        }
      }
      
  • 静态生命周期 'static
    • 所有的字符串字面值都拥有 'static 生命周期
      • let s: &'static str = "I have a static lifetime.";
  • 生命周期省略规则(如果代码符合这些场景,就无需明确指定生命周期)
    • 第一条规则是编译器为每一个引用参数都分配一个生命周期参数
      • fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str
    • 第二条规则是如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数
      • fn foo<'a>(x: &'a i32) -> &'a i32
    • 结构体中的方法的所有返回的借用默认 self 的生命周期
其他文章
交流区
加载中...