Lifetime bound compilation issue

Hello,

I'm trying to compile the below code, but can't seem to figure out the issue. The Updater trait is a simplified version of a builder pattern trait I am trying to implement.

pub trait Updater<I> {
    fn update_i<IN>(self, i: IN) -> impl Updater<IN>;
}

pub struct MyUpdater<'a, I>(std::marker::PhantomData<&'a I>);

impl<'a, I> Updater<I> for MyUpdater<'a, I> {
    fn update_i<IN: 'a>(self, i: IN) -> MyUpdater<'a, IN> {
        MyUpdater(std::marker::PhantomData)
    }
}

The compiler gives the below information, which isn't really helpful since I've already added the lifetime bound to IN:

error[E0309]: the parameter type `IN` may not live long enough
 --> src/main.rs:8:41
  |
7 | impl<'a, I> Updater<I> for MyUpdater<'a, I> {
  |      -- the parameter type `IN` must be valid for the lifetime `'a` as defined here...
8 |     fn update_i<IN: 'a>(self, i: IN) -> MyUpdater<'a, IN> {
  |                                         ^^^^^^^^^^^^^^^^^ ...so that the type `IN` will meet its required lifetime bounds...
  |
note: ...that is required by this bound
 --> src/main.rs:5:29
  |
5 | pub struct MyUpdater<'a, I>(std::marker::PhantomData<&'a I>);
  |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: consider adding an explicit lifetime bound
  |
8 |     fn update_i<IN: 'a + 'a>(self, i: IN) -> MyUpdater<'a, IN> {
  |                        ++++

Any help would be highly appreciated!

IIRC, that's a bug in RPITIT (return-position impl Trait in trait). If you remove it, your code compiles. I'll see if I can find the issue(s) somewhere, they were shared multiple times here on URLO shortly after 1.75 was released.

Adding 'a to Updater would also work:

pub trait Updater<'a, I> {
    fn update_i<IN: 'a>(self, i: IN) -> impl Updater<'a, IN>;
}

pub struct MyUpdater<'a, I>(std::marker::PhantomData<&'a I>);

impl<'a, I> Updater<'a, I> for MyUpdater<'a, I> {
    fn update_i<IN: 'a>(self, i: IN) -> impl Updater<'a, IN> {
        MyUpdater(std::marker::PhantomData)
    }
}

Playground.

I do think it is due to the capturing rules of RPITs, but I haven't fully grasped the mechanics of the capturing rules for lifetimes myself, so I can't really explain what the problem is when we elide the lifetime bound of the opaque type we return from update_i.

1 Like

Thanks for the pointers! Was not familiar with the capturing rules for lifetimes and I guess if there isn't any other available fix I will add a lifetime to the trait, which isn't great but is the only way I have rn for compiling :slight_smile:

I think it's that the implementation is more constrained than the trait (like so). The suggestion to add an explicit bound is bogus (in this playground and in the OP) as there is no lifetime that can be mentioned in the trait definition, only the specific implementation.

Are you sure GATs are applicable here? When I run your playground it doesn't compile, because the GAT is missing a bound IN : 'a, but if we add it, our implementation of Updater is not specific enough. Isn't adding a lifetime parameter to Updater the only option?

I was just rephrasing the requirements of the OP trait in a way that doesn't use opaque types in an attempt to demonstrate there is a real problem here: the implementation has limitations not expressible in terms of the trait (unless the trait is modified). That is, I don't think the OP compilation error is due to a RPITIT/compiler bug. (The diagnostics aren't good though.)

Let's say the OP could compile. Now consider a generic function with I, T: Updater<I> that calls update_i::<S>, where there exists some MyUpdater<'a, I> such that My<'a, S> is not a valid type. What happens when you pass MyUpdater<'a, I> as T if the OP could compile? The bounds and implementation say you should be able to, but the only two results I can think of are UB or C++-esque template nightmares (where the call site fails even though the bounds are met).

Here's another option. It's unclear to me if it's acceptable to the OP or not.

1 Like