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

Viewed 823

I want to store a collection of children each holding a reference to its parent.

The following example won't compile (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);
}

Is there a way to create such a cyclic reference without introducing smart pointers but with plain references? Maybe some unsafe can help?

If not, what might a minimal working example look like?

UPD: to ensure that the parent won't be dropped while a child is created, let's introduce a special method of Parent, but it also won't compile for an obvious reason:

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

UPD2: what if there may be no "orphan" children and they should always be owned by a parent?

2 Answers

Edit: Watch this youtube video - it goes over all the options.

I've found the best solution to this, is to not store the relationships in the parent, but to make them available through a method like 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

Well, this compiles: 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);
}

But then of course to dereference the raw pointer you'll need unsafe blocks:

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

EDIT: Let's add a bit of discussion, though. The above code does, in a very literal sense, what OP is asking for. However, there's certainly some flaws here. Most importantly, it's now up to the programmer using Parent and Child to ensure that no weird shit happens. If you initialize a child with one parent, and then the parent gets moved, boom, the pointer is now pointing to something it shouldn't be pointing to.

So now the requirement becomes: A child's parent must never be moved; or, if parent does get moved, the parent pointer of each of its children needs to be updated.

So to enforce that, you should probably encapsulate your raw pointer stuff in one way or another; the reason I didn't write about that is that you specifically said "no smart pointers pls" and I feel like anything that helps you enforce the security requirements will be some form of more or less sophisticated smart pointer.

Related