I've got into some weird GAT lifetimes error and hope someone can help me here.
At first some requirements:
Context comes from outside, I cannot control it and it can be non-'static.
Context defines a Node which it can create.
Context lifetime is not bound to Node and it can dropped at any time (or live longer).
Node has a generic lifetime, so outside code can make them reference each other
this tree of Node references is always one-way (non-cyclic) and I can enforce it (not part of the example)
For this to work I need to return &'a Node<'a> (all nodes have the same lifetime and can reference each other).
But it fails to compile with some strange error related to Ctx not being alive long enough.
Even more strange is that it is related to lifetime I return &'a. If I remove it - everything works. But i can't make a references tree.
Everything works if I remove "G" from GAT (lifetime from Node) but I need that "G".
So the question is why on earth does this &'a bounds Ctx lifetime and how to fix it.
PS: I know about using indexes but it doesn't fit my design goal. I'd like to make everything work by using references and lifetimes only (no vec[index], Rc, Arc, etc.).
Code:
trait Context {
type Node<'a>;
fn new_node<'a>(&mut self) -> Self::Node<'a>;
}
struct Storage<'a> {
vec: Vec<NonNull<()>>,
phantom: PhantomData<&'a mut ()>,
}
impl<'a> Storage<'a> {
fn allocate<Ctx: Context>(
&mut self,
context: &mut Ctx,
// vv removing this 'a "fixes" the problem
) -> &'a mut Ctx::Node<'a> {
// dummy placeholder implementation here
let mut ptr = NonNull::from_ref(Box::leak(Box::new(context.new_node())));
self.vec.push(ptr.cast());
unsafe { ptr.as_mut() }
}
}
fn new_node<Ctx>(context: &mut Ctx, storage: &mut Storage)
where
Ctx: Context,
{
storage.allocate(context); // error here
}
Error:
error[E0311]: the parameter type `Ctx` may not live long enough
|
141 | pub fn new_node<Ctx>(context: &mut Ctx, storage: &mut Storage)
| ------- the parameter type `Ctx` must be valid for the anonymous lifetime defined here...
...
145 | storage.allocate(context);
| ^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `Ctx` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound
|
141 ~ pub fn new_node<'a, Ctx>(context: &mut Ctx, storage: &mut Storage<'a>)
142 | where
143 ~ Ctx: Context + 'a,
|
&'a mut Node<'a> in rust is the infamous "borrowing forever" anti-pattern. exclusive references (a.k.a. mut borrows) are invariant over the type, meaning the compiler cannot shorten the lifetime parameter of the type, resulting a self-borrowing situation that lasts forever, see:
for tree like data structure with arena allocated nodes, it is common to use Cell<Option<&'a Node<'a>>> for internal pointers.
shared references are covariant over the type, and they are also Copy, so you don't have the borrowing forever problem, and Cell is used to achieve shared mutability (a.k.a. interior mutability).
error[E0311]: the parameter type `Ctx` may not live long enough
|
141 | pub fn new_node<Ctx>(context: &mut Ctx, storage: &mut Storage)
| ------- the parameter type `Ctx` must be valid for the anonymous lifetime defined here...
...
145 | storage.allocate(context);
| ^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `Ctx` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound
|
141 ~ pub fn new_node<'a, Ctx>(context: &mut Ctx, storage: &mut Storage<'a>)
142 | where
143 ~ Ctx: Context + 'a,
|
You haven't specified what lifetime the Storage argument has, so you get an implicit default. It should be Storage<'a>. The latest version of Rust warns about this footgun by default.
This is much more closer example to what I have in the real project:
trait Context {
type Node<'a>;
fn new_node<'a>(&mut self) -> Self::Node<'a>;
}
struct MyScope<'context, 'storage, Ctx> {
context: &'context mut Ctx,
storage: &'storage mut Vec<()>,
}
impl<'context, 'storage, Ctx> MyScope<'context, 'storage, Ctx> {
fn new_node_scope_ctx<F>(&mut self, f: F)
where
Ctx: Context,
F: FnOnce(&mut Ctx::Node<'storage>),
{
self.new_node_scope_any(f)
}
fn new_node_scope_any<Node, F>(&mut self, f: F)
where
Node: 'storage, // removing this makes code compile but I need that lifetime
F: FnOnce(&mut Node),
{
todo!()
}
}
Same error:
error[E0309]: the parameter type `Ctx` may not live long enough
|
162 | impl<'context, 'storage, Ctx> MyScope<'context, 'storage, Ctx> {
| -------- the parameter type `Ctx` must be valid for the lifetime `'storage` as defined here...
...
168 | self.new_node_scope_any(f)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `Ctx` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound
|
162 | impl<'context, 'storage, Ctx: 'storage> MyScope<'context, 'storage, Ctx> {
| ++++++++++
Why is Ctx becomes bound to 'storage lifetime? I'm not even using it with that lifetime, only GAT provided by it.