rust 具有相同结构的父代的Rc/RefCell

ffscu2ro  于 2022-11-12  发布在  其他
关注(0)|答案(3)|浏览(166)

我正在尝试将一些面向对象的代码转换成Rust。在我遇到这种情况之前,一切都很顺利。

struct A {
    root: Rc<RefCell<B>>,
}

struct B {
    parent: Weak<RefCell<B>>,
    c_lst: Vec<C>
    value: u32
}

struct C {
    parent: Weak<RefCell<B>>,
    b_lst: Vec<Rc<RefCell<B>>>,
    end: bool
}

我有三个结构体。我想让一个主结构体(这里是A)保存结构体B的根。BC需要以只读方式访问父值,但在第一个Rc出现的地方,它们需要是可变的。
开始时,调用此函数:

impl A {
    fn register(&mut self) {
        for c in self.root.borrow_mut().c_lst {
            c.parent = Rc::downgrade(&self.root);
        }
    }
}

然后我在A上使用一个update函数:

impl A {
    fn update(&mut self) {
        self.root.borrow_mut().update();
    }
}

impl B {
    fn update(&mut self) {
        for c in &mut self.c_lst {
            c.update();
        }
    }
}

impl C {
    fn update(&mut self) {
        match self.parent.upgrade() {
            Some(parent) => {
                // Fail because parent is already borrowed to call this update                       
                if parent.try_borrow().unwrap().value == 0 {
                    // Do stuff
                }
            },
            None => Panic!("Parent was freed")
        }
        if some_condition {
            self.spawn_b();
        }
        for b in &self.b_lst {
            b.borrow_mut().update();
        }
    }

    // This infinite loop in this state, but it's for the example
    fn spawn_b(&mut self) {
        let b = Rc::new(RefCell::new(B::new()));
        b.borrow_mut().parent = self.parent.clone();
        if !self.end {
            b.borrow_mut().c_lst = vec![C::new(Rc::downgrade(&b)), C::new(Rc::downgrade(&b))];
            for c in b.borrow_mut().c {
                c.spawn_b();
            }
        }
        self.b_lst.push(b);
    }
}

正如您在代码中看到的,我无法访问我的父状态。有什么建议吗?

klr1opcd

klr1opcd1#

示例中的Rc/Weak树是在Rust中实现带有父引用的树的一种简单方法。唯一的问题是,update函数需要对更新节点的可变访问,这会锁定较低的节点访问父节点。
您将不得不进行架构更改来解决此问题。以下是几种方法:

  • 将数据拆分为不同的RefCell甚至Cell。在您自己的节点更新时锁定主数据,然后在递归遍历子节点之前解锁主数据,让它们可以访问。
  • 分两个阶段处理树:一种方法是只使用不可变的借用将更改收集到列表中,另一种方法是不可变地在本地应用存储的更改。
  • 移动到邻接列表表示,其中每个节点的关系不存储在节点对象本身中,而是存储在单独的Map对象中,可以根据需要锁定该Map对象。
ckocjqey

ckocjqey2#

尽量避免在Rust中编写“面向对象”的代码。更喜欢组合而不是继承。如果你使用Rc来表示一个树形结构,那么要非常小心你指向“父”的方式。如果父也是Rc,那么你将有内存泄漏,因为那会产生一个循环。要打破这些,你可以使用Weak参考。也许阅读这本书的section会对你有帮助。

ef1yzkbh

ef1yzkbh3#

最后我使用了原始ptr。根据定义,这个解决方案使用的是unsafe

struct A {
    root: B,
}

struct B {
    parent: *const B,
    c_lst: Vec<C>
    value: u32
}

struct C {
    parent: *const B,
    b_lst: Vec<B>,
    end: bool
}

impl C {
    fn spawn_b(&mut self) {
        let new_b = B::new();
        // We push our b to Vec, to get his final memory address on the heap,
        // then we access it through mutable reference
        self.b_lst.push(new_b);
        let b: &mut B = self.b_lst.get_mut(self.b_lst.len() - 1).unwrap();
        b.parent = self.parent.clone();
        if !self.end {
            b.c_lst = vec![C::new(b * as const Particle), C::new(b as * const Particle)];
            for c in b.c {
                c.spawn_b();
            }
        }
    }
}

重要的是要注意,当一个新的B被推到C.b_lst,他的内存地址不必改变,如果你只推,它会工作,但使用remove()swap_remove()将改变B的位置和他们的内存地址,儿童家长将指向一个坏B,或恐慌。
要解决这个问题,要么在Vec上创建您的实现,以将B保持在创建的位置,要么当B地址发生变化时,在所有子级上进行迭代,以将新地址重新分配给父级。
我检查这个作为答案,但如果有人找到一个更好的解决方案与钢筋混凝土我会改变。
编辑:当创建Vec<B>时,使用固定大小的Vec::with_capacity(),或者其他crate。重要的是向量不会被重新分配。或者再次迭代所有子节点以应用新的父地址。

相关问题