在 rust 中使用 Rc 了解内存泄漏

Understanding memory leakage with Rc in rust

如果我们看下面的代码(playground link):

use std::cell::RefCell;
use std::rc::Rc;

struct Person {
    name: String,
    parent: Option<Rc<RefCell<Person>>>,
    child: Option<Rc<RefCell<Person>>>,
}

impl Drop for Person {
    fn drop(&mut self) {
        println!("dropping {}", &self.name);
    }
}

pub fn main() {
    let anakin = Rc::new(RefCell::new(Person {
        parent: None,
        child: None,
        name: "anakin".to_owned(),
    }));

    let luke = Rc::new(RefCell::new(Person {
        parent: None,
        child: None,
        name: "luke".to_owned(),
    }));

    luke.borrow_mut().parent = Some(anakin.clone());
    anakin.borrow_mut().child = Some(luke.clone());

    println!("anakin {}", Rc::strong_count(&anakin));
    println!("luke {}", Rc::strong_count(&luke));
}

代码运行时,不会打印来自 drop 实现的消息,因为这段代码应该会泄漏内存。

当代码完成时,luke 和 anakin 的计数应该以 1 而不是 0 结束(这是清理托管堆数据的时间)。

为什么计数最终没有为 0?我原以为会发生以下顺序:

  1. 我们从堆中的两个数据位置开始,一个由 luke 和 anakin.child 指向,另一个由 anakin 和 luke.parent 指向。对象 luke 拥有 luke.parent。阿纳金拥有的对象 anakin.child

  2. luke 超出范围,这意味着它拥有的成员也被删除。所以 luke 和 luke.parent 放弃了。两者都是 Rcs,因此两个内存位置的引用计数因此下降到 1

  3. anakin 被丢弃,导致 Rc 对象 anakin 和 anakin.child 被丢弃,这导致两个内存位置的引用计数下降到 0。

这是应该清理堆数据的地方?一个对象的成员是不是在删除时不删除?

当我删除或评论通过 borrow_mut 连接 luke 和 anakin 的行时,掉落按预期发生并且消息被正确打印。

  1. luke goes out of scope, which means its members which it owns are also dropped. So luke and luke.parent drop. Both are Rcs, so the ref count for both the memory locations therefore goes down to 1

这是你误会的步骤。

Are the members of an object not dropped when it is dropped?

没错。减少的计数是与 Rc 指向的数据直接相关的计数。当 luke 超出范围时,Rc 超出范围,并且引用 lukeRefCell 的事物计数减少,代码对 [=15 一无所知=].

在您的代码中,

luke.borrow_mut().parent = Some(anakin.clone());
anakin.borrow_mut().child = Some(luke.clone());

有 4 个 Rc 个对象(堆栈中有 2 个,堆中有 2 个),堆中有 2 个 RefCell 对象,并带有与之关联的计数。如您所见,每个 RefCell 的计数为 2,因为有 2 个 Rc 引用每个 RefCell.

anakin 下降时,它的 RefCell 的计数会减少,因此您在堆上的 luke RefCell 的计数为 2 和计数为 1.

anakin RefCell

luke 下降时,那会减少 that RefCell 的计数,因此每个现在都有 1 的计数。您最终在堆栈上没有引用 RefCellRc 值,但每个 RefCell 引用 other RefCell,所以Rust 无法知道它们可以安全删除。

我不能完全从你的问题中看出,但这绝对是 Rc 类型与 RefCell 一起使用时的预期限制,因为它允许在对象所有权中引入循环.

您的代码几乎是 Rc 循环的最小可重现示例:What is a minimal example of an Rc dependency cycle?