Files
LearnRustEasy/src/chapter_3/chapter_3_16_7.md
2023-05-18 10:13:09 +08:00

2.5 KiB
Raw Blame History

3.16.7 引用循环、内存泄露、Weak智能指针

1. 引用循环和内存泄露

下图是一个循环链表: 注释

下面的代码试图用之前学到的智能指针相关知识实现上面的链表:

use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;

#[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());

    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());

    if let Some(link) = a.tail() {
        *link.borrow_mut() = Rc::clone(&b); //将a修改为指向b
    }

    println!("b rc count after changing a = {}", Rc::strong_count(&b)); //输出引用计数为2
    println!("a rc count after changing a = {}", Rc::strong_count(&a)); //输出引用计数为2

    //下面的调用将出错因为上面已经制造了循环引用编译器无法找到tail
    // println!("a next item = {:?}", a.tail());
}

下面分析整个过程中的内存布局:

  • 当执行完第26行后内存布局如下

注释

  • 在执行完第33行后b对应的Rc引用计数变成2内存布局如下

注释

此时如果第40行代码执行将会panic因为已经成了一个循环链表Rust无法匹配到a的tail最终会造成栈溢出。

  • 在最后离开作用域时Rust将会对b和a调用drop方法对b调用drop方法后内存布局如下

注释

此时b的Rc实例引用计数减去1但是仍然不为0因为第33行让a也引用了b的Rc实例所以b所指向的内存并不会被释放。

  • 然后Rust尝试drop a其对应的Rc示例引用计数减去1但仍然不为0所以调用a的drop后内存布局为

注释

至此,造成内存泄露。

2. 使用弱引用Weak

Weak类似于Rc但它不持有所有权它仅仅保存一份指向数据的弱引用。弱引用就是不保证引用的对象存在如果不存在就返回一个 None。 下面为Weak和Rc的对比