如何使用普通引用创建一个简单的 Parent-Children 结构?

How to create a simple Parent-Children structure with the plain references?

我想存储一个 children 的集合,每个集合都有一个对其 parent 的引用。

以下示例无法编译 (playground)。

struct Parent<'p> {
    children: Vec<Child<'p>>,
}

struct Child<'p> {
    parent: &'p Parent<'p>
}

fn main() {
    let mut parent = Parent {children: vec![]};
    let child = Child{parent: &parent};
    parent.children.push(child);
}

有没有一种方法可以在不引入智能指针但使用普通引用的情况下创建这样的循环引用?也许一些 unsafe 可以帮助?

如果不是,最小的工作示例是什么样的?

UPD:为了保证parent在创建child时不会被丢弃,我们引入一个特殊的方法Parent,但它也不会编译,原因很明显:

impl<'p> Parent<'p> {
    fn add_child(&'p mut self) {
        let child = Child{parent: &self};
        self.children.push(child);
    }
}

UPD2:如果可能没有“孤儿”children 并且他们应该始终由 parent 拥有怎么办?

嗯,这个编译: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=de9612b8b7425f8b1e77e6065d76b193

struct Parent {
    children: Vec<Child>,
}

struct Child {
    parent: *const Parent
}

fn main() {
    let mut parent = Parent {children: vec![]};
    let child = Child{parent: &parent as *const Parent};
    parent.children.push(child);
}

但是当然要取消引用原始指针,您将需要不安全的块:

let first_child = &parent.children[0];
let the_parent = unsafe { &*first_child.parent};

编辑:不过,让我们添加一些讨论。从字面上看,上面的代码确实满足了 OP 的要求。但是,这里肯定存在一些缺陷。最重要的是,现在由程序员使用 ParentChild 来确保不会发生奇怪的事情。如果你用一个 parent 初始化 child,然后 parent 被移动,砰的一声,指针现在指向它不应该指向的东西。

所以现在要求变成:A child的parent绝不能移动;或者,如果 parent 确实移动了,则需要更新其每个 children 的 parent 指针。

因此,要强制执行此操作,您可能应该以某种方式封装原始指针内容;我没有写这个的原因是你特别说过“请不要使用智能指针”,我觉得任何能帮助你执行安全要求的东西都会是某种形式的或多或少复杂的智能指针。

编辑:Watch this youtube video - 它涵盖所有选项。

我找到了最好的解决方案,就是不将关系存储在父级中,而是通过 get_child():

这样的方法使它们可用
struct Parent {
    // Only store the data, the child doesn't need to
    // know who it belongs to at this point
    children: Vec<Data>,
    name: &'static str,
}

impl Parent {
    // Just add child data, you still don't need to
    // know the relationship at this point
    fn add_child(&mut self, name: &'static str) {
        let child = Data{name};
        self.children.push(child);
    }

    // OK, now you can get a child, which knows who it's parent is
    // Note that while the result of this function exists, the parent
    // must be immutable
    fn get_child<'a>(&'a self, idx: usize) -> Child<'a> {
        Child{parent: self, data: &self.children[idx]}
    }
}

// Just the inside data of the child, what the parent actually owns
struct Data {
    name: &'static str
}

// This child knows who his parent is and keeps a
// reference to the parent and his data.
// While he exists, the parent cannot be changed
struct Child<'p> {
    parent: &'p Parent,
    data: &'p Data,
}

fn main() {
    let mut parent = Parent {children: vec![], name: "Robert"};
    parent.add_child("Bob");
    let bob = parent.get_child(0);
    println!("Got Child: {}, child of {}", bob.data.name, bob.parent.name);

    // The borrow checker is smart, and allows modification of the parent
    // as long as you never touch bob again
    parent.add_child("Lucas");
    let lucas = parent.get_child(1);
    println!("Got Child: {}, child of {}", lucas.data.name, lucas.parent.name);
    
    // If you uncomment this code, it'll fail, to prevent the situation:
    // 1. You hold a reference to bob at memory address 1234
    // 2. You've called add_child() above; this may have caused the
    //    vec to resize (realloc()) and move all the elements around
    // 3. Now your bob reference is invalid because the vec moved it to 1212
    //    After calling get_mem/realloc
    //println!("Got Child: {}, child of {}", bob.data.name, bob.parent.name);
}

Playground link