neon64
neon64

Reputation: 1516

Trying to split out self-referential data into a seperate struct

I want to be able to store a struct called Child inside a Parent, where the Child contains a reference back to the parent.

It works if I have the Child structs directly inside the parent like this:

struct Parent<'s> {
    cache: RefCell<Vec<Child<'s>>>
}

But if I move the Vec into a separate struct, then it will fail to compile with lifetime errors.

struct Parent<'s> {
    cache: RefCell<Cache<'s>>
}

struct Cache<'s> {
    children: Vec<Child<'s>>
}

It is possible to make this example work with the separate structs?

Here's the full working code, which compiles fine. When move the children into the separate struct then it fails.

My analysis of the problem:

When Parent contains children directly, 's is the same lifetime as the scope of the Parent struct itself, thus I can call methods that take &'s self on Parent.

When Parent contains Cache which contains children, 's is the same lifetime as the scope of the Cache struct, which is created before Parent, thus it is impossible to call methods on Parent that take &'s self. Attempting to do so gives the error

<anon>:33:15: 33:16 error: `p` does not live long enough
<anon>:33     let obj = p.create_object();
                        ^
<anon>:30:48: 38:2 note: reference must be valid for the block suffix following statement 0 at 30:47...
<anon>:30     let cache = Cache { children: Vec::new() }; // the lifetime `'s` is essentially from this line to the end of the program
<anon>:31     let mut p = Parent { cache: RefCell::new(cache) }; // although the Parent instance was created here, 's still refers to the lifetime before it
<anon>:32     // this fails because p doesn't live long enough
<anon>:33     let obj = p.create_object();

I need a way of shortening 's to the scope of Parent, not the scope of the Cache.

Disclaimer: This question is very similar to one I asked earlier (https://stackoverflow.com/questions/32579518/rust-lifetime-error-with-self-referencing-struct?noredirect=1#comment53014063_32579518) that was marked as duplicate. I've read through the answer and I believe I'm beyond that as I can get the lifetimes of references right (as shown in my first example). I'm asking this (now slightly different) question again because I now have a concrete example that works, and one that doesn't work. I'm sure that what can be done with one struct can be done with two, right?

Upvotes: 2

Views: 105

Answers (1)

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65822

You can make it compile by forcing the Cache and the Parent to have the same lifetime by defining them in the same let binding.

fn main() {
    let (cache, mut p);
    cache = Cache { children: Vec::new() };
    p = Parent { cache: RefCell::new(cache) };
    let obj = p.create_object();

    let c1 = Child { parent: &p, data: 1 };
    p.cache.borrow_mut().children.push(c1);
}

Here, we're essentially declaring a destructured tuple and then initializing it. We cannot initialize the tuple directly on the let binding:

    let (cache, mut p) = (Cache { children: Vec::new() }, Parent { cache: RefCell::new(cache) });

because the initializer for p references cache, but that name is not defined until the end of the let statement. The separate initialization works because the compiler tracks which variables are initialized; if you swap the order of the assignments, you'll get a compiler error:

<anon>:31:38: 31:43 error: use of possibly uninitialized variable: `cache` [E0381]
<anon>:31     p = Parent { cache: RefCell::new(cache) };

Upvotes: 2

Related Questions