十三、智能指针

148 阅读8分钟

十三、智能指针

1. 基本概念

  • 指针:包含内存地址的变量
    • Rust 中,最常见的指针是 引用 ,除了引用数据外,没有其他任何特殊功能,也没有额外开销
    • 跟 C 语言的指针概念是类似的
  • 智能指针:一类数据结构,表现类似于指针,但拥有额外的元数据和功能
    • 起源于 C++,在其他语言中也存在
    • 通常使用结构体实现
    • 与普通指针的区别:普通指针是一类只 借用 数据的指针;大部分情况下,智能指针会 拥有 其指向的数据
    • 与一般结构体的区别:智能指针实现了 DerefDrop 两个 trait
      • Deref:允许智能指针结构体实例表现得像引用一样
      • Drop:允许自定义运行于智能指针离开作用域后的代码

2. Box<T>

  • 特性
    • 指向堆上的数据,允许你将一个值放到堆上而不是栈上,栈中只保留指向堆数据的指针
    • 没有性能损失,也没有多少额外功能
  • 常用场景
    • 当有一个在编译时未知大小的类型,而又想要在需要知道确切大小的上下文中使用这个类型值时
    • 当有大量数据,并希望在确保数据不被拷贝的情况下转移所有权时
    • 当希望拥有一个值,并只关心它的类型是否实现了特定 trait ,而不关心其具体类型时
    • 例如:创建递归类型
  • 注意:Rust 在编译时,就需要知道某个类型要占用多少空间
  • Box<T> 实现了 DerefDrop 两个 trait

3. Deref trait

  • 来源: std::ops::Deref

  • deref: 解引用

  • 一个 struct 实现这个 trait 后,这个 struct 类型的引用就可以重载 解引用运算符 了,对应的引用就可以使用解引用运算符 *

  • DerefMut trait 作用与 Deref 作用相似,唯一不同的是 DerefMut 用于重载可变引用的 * 运算符

  • 使用示例

    // 来自官方教程的一个示例
    
    struct MyBox<T>(T);
    
    impl<T> MyBox<T> {
      fn new(x: T) -> MyBox<T> {
        MyBox(x)
      }
    }
    
    use std::ops::Deref;
    
    impl<T> Deref for MyBox<T> {
      type Target = T;
    
      fn deref(&self) -> &T {
        &self.0 // 返回当前值的引用
      }
    }
    
    fn main() {
      let x = 5;
      let y = MyBox::new(x);
    
      assert_eq!(5, x);
      assert_eq!(5, *y);
    }
    
    // rust 底层运行 *y 时,实际上是运行了: *(y.deref())
    // 如果 deref 方法直接返回值而不是值的引用,就会导致这个值的所有权被移出 self,从而导致问题
    
    
  • 解引用强制多态

    • 将实现了 Deref 的类型的变量的引用,转换为可通过 Deref 转换的 原始类型 类型的变量的引用
    • 简言之,如果一个引用类型是 可解引用的 ,则通过解引用运算符 * 对其解引用时,会对这个引用值递归执行解引用,直到不能继续解引用为止
    • 强制进行解引用强制多态
      • T: Deref<Target=U> 时从 &T&U
      • T: DerefMut<Target=U> 时从 &mut T&mut U
      • T: Deref<Target=U> 时从 &mut T&U
        • Rust 也会将可变引用强转为不可变引用
      • 简单地说:如果你需要对某个变量 a 进行解引用,如果 a 解引用的值还可解引用,则会继续对 a 进行解引用得到它的值 b,直到获取到的值是一个不可解引用的值为止,像一个递归的过程一样

4. Drop trait

  • 作用:当值要离开作用域时,允许执行一些额外的代码

  • 特性

    • 可以为任何类型提供 Drop trait 的实现,这段代码也将被用于释放各类资源,如文件、网络连接等
    • 当实例离开作用域 Rust 会自动调用 drop 方法,变量以被创建时 相反 的顺序被丢弃
    • drop 方法不允许被主动调用,如果要提前强制释放变量,可以使用 std::mem::drop
  • 实现

    • 要求实现一个 drop 方法,这个方法只有一个入参:&mut self

      // 官方教程的一个示例
      struct CustomSmartPointer {
        data: String,
      }
      
      impl Drop for CustomSmartPointer {
        fn drop(&mut self) {
          println!("Dropping CustomSmartPointer with data `{}`!", self.data);
        }
      }
      
      fn main() {
        let c = CustomSmartPointer { data: String::from("my stuff") };
        let d = CustomSmartPointer { data: String::from("other stuff") };
        println!("CustomSmartPointers created.");
      }
      
      // 变量 d 比变量 c 先被销毁
      // 原因:这些变量的引用是保存在栈中,栈是后进先出
      
      // 续:显示清理变量
      use std::mem::drop;
      
      fn main() {
          let c = CustomSmartPointer { data: String::from("some data") };
          println!("CustomSmartPointer created.");
          drop(c);
          println!("CustomSmartPointer dropped before the end of main.");
      }
      
      

5. Rc<T>

  • Rc (reference counting) 即 引用计数

  • 作用:启用多所有权。当我们希望在堆上分配一些内存,这些内存供程序的多个部分读取,且无法在编译时确定程序的哪一部分会最后结束使用它时,就可以使用引用计数。

  • 注意:这个智能指针 只能 用于单线程场景

  • 注意:Rc<T> 只允许在程序的多个部分之间只读地共享数据,不然会导致数据竞争和不一致

  • 使用示例

    // 来自官方教程的一个示例
    
    use crate::List::{Cons, Nil};
    use std::rc::Rc;
    
    enum List {
        Cons(i32, Rc<List>),
        Nil,
    }
    
    fn main() {
        let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
        println!("count after creating a = {}", Rc::strong_count(&a));
        let b = Cons(3, Rc::clone(&a)); // 相当于 `浅拷贝`,只会增加引用计数,不会真实拷贝,无性能问题
        println!("count after creating b = {}", Rc::strong_count(&a));
        {
            let c = Cons(4, Rc::clone(&a));
            println!("count after creating c = {}", Rc::strong_count(&a));
        }
        println!("count after c goes out of scope = {}", Rc::strong_count(&a));
    }
    
    

6. RefCell<T>

  • 内部可变性:Rust 中的一个特定设计,允许 developer 即使在有不可变引用时也可以改变数据

    • 含义:在不可变值的内部改变值
    • 类似于 ECMAScript 6 中用 const 定义的对象,虽然不可以直接为这个变量重新赋值,但可以为这个对象的属性重新赋值
  • RefCell<T> 具有数据的唯一所有权

  • 与其他引用的借用规则的区别

    • 对于引用和 Box<T>,它们的借用规则的不可变性作用于编译时
      • 违反规则时,会导致编译错误
    • 对于 RefCell<T> ,它的借用规则的不可变性作用于运行时
      • 违反规则时,会导致 panic
  • 注意:这个智能指针 只能 用于单线程场景

  • 注意: RefCell 在任何时候只允许有多个不可变借用或一个可变借用

    • 如果违反这个规则,就会在运行时 panic!
  • 智能指针的对比

    BoxRcRefCell
    所有者单一多个单一
    编译时借用检查不可变或可变不可变不可变
    运行时借用检查--不可变或可变
  • 借用规则的一个推论:对于一个不可变值,不能可变地借用这个值

    fn main() {
        let b = Box::new(55);
        let mut b1 = &mut b; // 这是不允许的,会报错
    
        let mut c = Box::new(55);
        let mut c1 = &mut c; // 这是允许的,因为 c 是可变的
    }
    
  • RefCell<T> 的常用方法

    • borrow : 获取不可变引用
      • 返回 Ref 类型的智能指针
    • borrow_mut : 获取可变引用
      • 返回 RefMut 类型的智能指针

7. 引用循环与内存泄露

  • Rust 并不能保证完全避免内存泄露

  • 创建了循环引用会导致一些内存不会被释放,从而导致内存泄露

  • 循环引用的示例

    // 来自官方教程的一个示例
    
    use std::rc::Rc;
    use std::cell::RefCell;
    use crate::List::{Cons, Nil};
    
    #[derive(Debug)]
    enum List {
      Cons(i32, RefCell<Rc<List>>),
      Nil,
    }
    
    impl List {
      fn tail(&self) -> Option<&RefCell<Rc<List>>> {
        match self {
          Cons(_, item) => Some(item),
          Nil => None,
        }
      }
    }
    
    use crate::List::{Cons, Nil};
    use std::rc::Rc;
    use std::cell::RefCell;
    #[derive(Debug)]
    enum List {
        Cons(i32, RefCell<Rc<List>>),
        Nil,
    }
    
    impl List {
        fn tail(&self) -> Option<&RefCell<Rc<List>>> {
            match self {
                Cons(_, item) => Some(item),
                Nil => None,
            }
        }
    }
    
    fn main() {
        let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
    
        println!("a initial rc count = {}", Rc::strong_count(&a));
        println!("a next item = {:?}", a.tail());
        
        // 让 b 的尾部指向 a
        let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
    
        println!("a rc count after b creation = {}", Rc::strong_count(&a));
        println!("b initial rc count = {}", Rc::strong_count(&b));
        println!("b next item = {:?}", b.tail());
    
        // 让 a 的尾部指向 b,从而产生循环引用
        if let Some(link) = a.tail() {
            *link.borrow_mut() = Rc::clone(&b);
        }
    
        println!("b rc count after changing a = {}", Rc::strong_count(&b));
        println!("a rc count after changing a = {}", Rc::strong_count(&a));
    
        // Uncomment the next line to see that we have a cycle;
        // it will overflow the stack
        // println!("a next item = {:?}", a.tail());
    }
    
    
  • 避免循环引用

    • Rc<T> 变为 Weak<T>
    • Weak<T> : 弱引用计数的智能指针
    • weak_count : 不为 0 时也可被清理
    • 创建弱引用
      • Weak::new()
      • Rc::downgrade(&target)
  • 关于强引用、弱引用的一个示例

    // 改编自官方教程的一个示例
    use std::cell::RefCell;
    use std::rc::{Rc, Weak};
    
    #[derive(Debug)]
    pub struct Node {
        value: i32,
        parent: RefCell<Weak<Node>>,
        children: RefCell<Vec<Rc<Node>>>,
    }
    
    fn show_counting(node: &Rc<Node>) {
        println!(
            "node's counting is: strong_count = {}, weak_count = {}",
            Rc::strong_count(&node),
            Rc::weak_count(&node),
        );
    }
    
    fn show_node(node: &Rc<Node>) {
        println!("node value = {}", node.value);
        if node.children.borrow().len() == 0 {
            println!("node doesn't have any children, it's a leaf");
        }
        // upgrade: 把弱引用升级为强引用。如果升级成功就会返回对应的 Rc,如果升级失败就返回 None
        match node.parent.borrow().upgrade() {
            None => println!("node doesn't have parent."),
            Some(parent_node) => println!("node's parent is {:?}", parent_node),
        }
        println!("--- --- ---")
    }
    
    pub fn create_tree() {
        // 叶节点,子节点为空数组
        let leaf = Rc::new(Node {
            value: 3,
            parent: RefCell::new(Weak::new()),
            children: RefCell::new(vec![]),
        });
    
        show_counting(&leaf);
        show_node(&leaf);
    
        // 枝节点,有一个子节点
        let branch = Rc::new(Node {
            value: 5,
            parent: RefCell::new(Weak::new()),
            children: RefCell::new(vec![Rc::clone(&leaf)]),
        });
    
        // downgrade: 把强引用降级为弱引用
        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
    
        show_counting(&leaf);
        show_node(&leaf);
    
        show_counting(&branch);
    }