Rust 解决循环引用

导航

循环引用

循环引用出现的一个场景就是你指向我我指向你,导致程序崩溃

解决方式 可以通过弱指针 ,而Rust中的弱指针就是Weak

Rc中,可以实现,对一个变量,持有多个 不可变引用,并且都拥有 该变量的所有权,这种情况下就可能会发生循环引用的现象

一、现象

从代码中来,到代码中去,先上个图

rust 复制代码
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() {
  //Cons(5, RefCell::new(Rc::new(Nil)))已经有一个拥有者a
  let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));

  println!("a的初始化rc计数 = {}", Rc::strong_count(&a));
  println!("a指向的节点 = {:?}", a.tail());

  // 创建`b`到`a`的引用
  //b指向的结构体Cons(10, RefCell::new(Rc::clone(&a)),结构体字段也拥有了Cons(5, RefCell::new(Rc::new(Nil))
  //对于Cons(10, RefCell::new(Rc::clone(&a)结构体,只有一个拥有者b
  let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
   //所以对于Cons(5, RefCell::new(Rc::new(Nil)))已经有了两个拥有者
  println!("在b创建后,a的rc计数 = {}", Rc::strong_count(&a));//这里是2
  println!("b的初始化rc计数 = {}", Rc::strong_count(&b));//这里是1
  println!("b指向的节点 = {:?}", b.tail());

  // 利用RefCell的可变性,创建了`a`到`b`的引用
  if let Some(link) = a.tail() {
      //因为 Cons(i32, RefCell<Rc<List>>)内部定义的是RefCell,可以修改指向,指向另一个Rc
      *link.borrow_mut() = Rc::clone(&b);//指向b
  }
  //对于Cons(10, RefCell::new(Rc::clone(&a)已经有b和a结构体中的Cons字段,两个所有者
  println!("在更改a后,b的rc计数 = {}", Rc::strong_count(&b));
   //对于Cons(5, RefCell::new(Rc::clone(&b)已经有a和b结构体中的Cons字段,两个所有者
  println!("在更改a后,a的rc计数 = {}", Rc::strong_count(&a));

  // 下面一行println!将导致循环引用
  //Rc指向的对象被drop的原则是,所有拥有者已经失去所有权,如果不主动drop,就无法解开引用
  // 下面一行println!将导致循环引用
    // 我们可怜的8MB大小的main线程栈空间将被它冲垮,最终造成栈溢出,Debug特征会一直打印每个结构体的字段,所以会递归
    // println!("a next item = {:?}", a.tail());
}

二、解决

Weak 非常类似于 Rc,但是与 Rc 持有所有权不同,Weak 不持有所有权 ,借用的值可以在生命周期结束 后正常drop掉,就不会出现循环引用了.

WeakRc不同的地方是:

  1. 获取一个没有所有权的引用方式是,Rc::downgrade(&)
  2. 获取借用值是upgrade(),返回的是Option<Rc> ,值存在就返回引用,不存在就返回None
rust 复制代码
use std::rc::Rc;
use std::rc::Weak;
use std::cell::RefCell;

// 主人
struct Owner {
    name: String,
    gadgets: RefCell<Vec<Weak<Gadget>>>,
}

// 工具
struct Gadget {
    id: i32,
    owner: Rc<Owner>,
}

fn main() {
    // 创建一个 Owner
    // 需要注意,该 Owner 也拥有多个 `gadgets`
    let gadget_owner : Rc<Owner> = Rc::new(
        Owner {
            name: "Gadget Man".to_string(),
            gadgets: RefCell::new(Vec::new()),
        }
    );

    // 创建工具,同时与主人进行关联:创建两个 gadget,他们分别持有 gadget_owner 的一个引用。
    let gadget1 = Rc::new(Gadget{id: 1, owner: gadget_owner.clone()});
    let gadget2 = Rc::new(Gadget{id: 2, owner: gadget_owner.clone()});

    // 为主人更新它所拥有的工具
    // 因为之前使用了 `Rc`,现在必须要使用 `Weak`,否则就会循环引用
    gadget_owner.gadgets.borrow_mut().push(Rc::downgrade(&gadget1));
    gadget_owner.gadgets.borrow_mut().push(Rc::downgrade(&gadget2));

    // 遍历 gadget_owner 的 gadgets 字段
    for gadget_opt in gadget_owner.gadgets.borrow().iter() {

        // gadget_opt 是一个 Weak<Gadget> 。 因为 weak 指针不能保证他所引用的对象
        // 仍然存在。所以我们需要显式的调用 upgrade() 来通过其返回值(Option<_>)来判
        // 断其所指向的对象是否存在。
        // 当然,Option 为 None 的时候这个引用原对象就不存在了。
        let gadget = gadget_opt.upgrade().unwrap();
        println!("Gadget {} owned by {}", gadget.id, gadget.owner.name);
    }

    // 在 main 函数的最后,gadget_owner,gadget1 和 gadget2 都被销毁。
    // 具体是,因为这几个结构体之间没有了强引用(`Rc<T>`),所以,当他们销毁的时候。
    //!!!!!!
    // 首先 gadget2 和 gadget1 被销毁。
    // 然后因为 gadget_owner 的引用数量为 0,所以这个对象可以被销毁了。
    // 循环引用问题也就避免了
}
相关推荐
一颗松鼠几秒前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
有梦想的咸鱼_2 分钟前
go实现并发安全hashtable 拉链法
开发语言·golang·哈希算法
海阔天空_20137 分钟前
Python pyautogui库:自动化操作的强大工具
运维·开发语言·python·青少年编程·自动化
天下皆白_唯我独黑15 分钟前
php 使用qrcode制作二维码图片
开发语言·php
夜雨翦春韭18 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
小远yyds20 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
何曾参静谧32 分钟前
「C/C++」C/C++ 之 变量作用域详解
c语言·开发语言·c++
q567315231 小时前
在 Bash 中获取 Python 模块变量列
开发语言·python·bash
许野平1 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
也无晴也无风雨1 小时前
在JS中, 0 == [0] 吗
开发语言·javascript