An adder that receives 2 lifetimed structs, can't add

I reduced my code to this minimum verifiable example:

use core::marker::PhantomData;

pub trait Engine<P> {
	fn add_assign(
		&self,
		left: &mut P,
		right: &P,
	) -> Result<(), ()>;
}

pub struct A<'a, R> {
    _phantom: PhantomData<&'a R>
}

pub fn add_both(a1: &mut A<'_, u64>,
		        a2: &A<'_, u64>,
		        engine: &dyn Engine<A<'_, u64>>) 
{
    engine.add_assign(a1,a2);
}

which won't compile due to lifetime problems. I don't know what is the problem

Why does this happen?

engine.add_assign(a1,a2);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'2`
   |

Your add_assign takes all parameters using a common P type, and your call is using P = A<'_, R>, but that forces them to have the same inferred lifetime. The shared a2: &A is covariant in A, so that can conform if you constrain its lifetime like 'a2: 'a1_and_engine, but &mut A and &dyn Engine<A> are invariant in A.

3 Likes

And here's how to make that happen: just use a single lifetime name for all the As that become P.

pub fn add_both<'a>(a1: &mut A<'a, u64>,
		        a2: &A<'a, u64>,
		        engine: &dyn Engine<A<'a, u64>>) 
{

('_ is not a lifetime name; it is syntax for an explicitly unspecified lifetime, and what happens instead is according to the lifetime elision rules, which in this case mean a distinct lifetime for each input parameter. So you must instead use a named lifetime.)

2 Likes

Is there another solution that does not require setting all lifetimes to be the same?

You will have to change the trait definition to not use a single parameter P, or to not use &mut P so P can be covariant (not applicable since you want to mutate). For example, if we look at the AddAssign trait,

pub trait AddAssign<Rhs = Self> {
    fn add_assign(&mut self, rhs: Rhs);
}

there is not a single type involved, but instead the two types Self and Rhs. Applying the same principle to your code:

pub trait Engine<P, Q> {
    fn add_assign(&self, left: &mut P, right: &Q) -> Result<(), ()>;
}

pub fn add_both<'a1, 'a2>(
    a1: &mut A<'a1, u64>,
    a2: &A<'a2, u64>,
    engine: &dyn Engine<A<'a1, u64>, A<'a2, u64>>,
) {
    engine.add_assign(a1, a2);
}
1 Like

No chance of making it work with anonymous lifetimes? Really needed it for other reasons on my code that are too complex to explain

If the lifetimes are allowed to be different, then the compiler no longer has a way to prevent a1 from being mutated to contain references that may become invalid before a1 does. That's the rule that actually matters, here. Perhaps there is a different way to express what you want, but it will not contain a lifetime inside of P.

Perhaps what you need to do is stop having a lifetime in A. Or perhaps there's another solution. It will be easier for us to suggest solutions if you provide an example of what A actually is and why it needs a lifetime parameter.

1 Like

You can get a little closer with generic associated types (GATs), but that means a type can't have multiple Engine implementations with different P, and it's also not object safe. Example:

use core::marker::PhantomData;

pub trait Engine {
    type P<'p>;
    
	fn add_assign(
		&self,
		left: &mut Self::P<'_>,
		right: &Self::P<'_>,
	) -> Result<(), ()>;
}

pub struct A<'a, R> {
    _phantom: PhantomData<&'a R>
}

pub fn add_both(a1: &mut A<'_, u64>,
		        a2: &A<'_, u64>,
		        engine: &impl for<'p> Engine<P<'p> = A<'p, u64>>) 
{
    engine.add_assign(a1,a2);
}

Depending on what you're doing with left and right, there might be a way to do something with implementations on A and trait bounds.

Unfortunately the idea was to have a runtime chosen engine, so I cannot be generic on which one is being used

If you're willing to dabble in nightly Rust, #95451 allows object safe GATs with an incomplete feature, #![feature(generic_associated_types_extended)].

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.