如何使用普通引用创建一个简单的父子结构?

3
我想存储一个包含对其父项的引用的子项集合。
以下示例无法编译(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);
}

有没有一种方法可以创建这样的循环引用,而不需要引入智能指针,只使用纯引用?也许一些不安全的操作可以帮助吗?

如果不行,可能会有一个最小的工作示例是什么样子的?

更新:为了确保在创建子项时父项不会被删除,让我们引入一个Parent的特殊方法,但出于明显的原因它也无法编译:

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

更新2: 如果没有“孤儿”子元素,它们应该始终由父元素拥有吗?


1
引用基本上是为了防止这种情况发生 - 想想当您的示例中的“Parent”被删除时会发生什么。 - Aiden4
我理解,但在我的情况下,我还希望以某种方式确保子对象始终归属于父对象。 - tsionyx
3
你需要使用Rc或者Arc- 实际上,在std::rc中给出的示例正是你想要实现的。 - Aiden4
2个回答

2

编辑:观看这个YouTube视频 - 它涵盖了所有选项。

我发现解决这个问题的最佳方法是不要在父级中存储关系,而是通过像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


0

嗯,这个可以编译:

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 的程序员需要确保不会发生奇怪的事情。如果你用一个父对象初始化一个子对象,然后移动了父对象,那么指针现在指向的就是不应该指向的东西。

所以现在的要求变成了:子对象的父对象不能被移动;或者,如果父对象确实被移动了,那么它每个子对象的父指针都需要更新。

因此,为了强制执行这一点,你可能应该将原始指针封装起来;我没有写关于这个的原因是你明确说了“不要智能指针”,而我觉得任何有助于你强制执行安全要求的东西都会是某种形式的更或多或少复杂的智能指针。


我不明白为什么会有负评?只要数据结构确保指针始终有效,这是一种有效且安全的选择。也许应该扩展答案以解释这一点,并提供一个示例安全接口?如果我们的“安全”接口无法维护应有的不变量,还可以说明为什么有人会选择使用带弱引用的Rc - effect
1
是的,这不是关于是否最佳实践的问题。我认为我永远不会这样做,而不是使用智能指针/ Rcs... - cadolphs
为了提供一个“安全”的接口,OP需要展示他们想要如何使用整个结构。 - cadolphs
1
@effect 虽然这个版本可能不会出现任何未定义的行为,但微小的更改将引入错误,并且编译器无法保护您。我认为在没有解释为什么它是安全的或记录安全要求的情况下建议使用“unsafe”是不负责任的。但即使加上这些补充,我认为这永远不可能是可靠的,因为没有机制可以阻止parent被移动(除非将其放入Box::pin中)。 - kmdreko
1
正确。保证某种安全的唯一方法是将父母和孩子都放在特定接口后面,作为更大结构的一部分或其他什么东西,以强制执行安全要求。 - cadolphs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接