Reputation: 1516
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
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