1

Working with generic types that have lifetimes attached to them (here, trait M<'m>), trait methods that work with them need to be generic over the input lifetime. I understand that genericity to be at the caller's choice: A function like fn work_with_m<'m>>(self, message: &'m impl M<'m>) could be read at "for any implementation of M and any lifetime 'm that has, I'm providing a function that …".

In the following code (playground link], when WrappingH implements the trait H, it seems the compiler is trying to work on the child with the same implementation lifetime 'im (which can't be satisfied, as it's using a local object) – but why does the compiler go that way rather than picking a shorter (one-line) lifetime to work with, during which both the type and the borrow exist?

trait M<'m> {}

struct WrappingM<'m, Inner: M<'m>>(&'m Inner);

impl<'m, I: M<'m>> M<'m> for WrappingM<'m, I> {}

trait H {
    fn work_with_m<'m>(&self, message: &'m impl M<'m>);
}

struct WrappingH<C: H> {
    child: C,
}

impl<C: H> H for WrappingH<C> {
    fn work_with_m<'im>(&self, message: &'im impl M<'im>) {
        let wrapped = WrappingM(message);
        // The child implements work_with_m for any lifetime of *my* choosing, how can't that be
        // enough? (`wrapped` only lives in here, but that's also some time for which it should be
        // implemented).
        self.child.work_with_m(&wrapped);
    }
}

impl H for () {
    fn work_with_m<'m>(&self, _: &'m impl M<'m>) {
        unimplemented!();
    }
}

fn main() {
    let _: WrappingH<()> = unimplemented!();
}

The error it is producing is

error[E0597]: `wrapped` does not live long enough
  --> src/lib.rs:21:32
   |
16 |     fn work_with_m<'im>(&self, message: &'im impl M<'im>) {
   |                    --- lifetime `'im` defined here
...
21 |         self.child.work_with_m(&wrapped);
   |         -----------------------^^^^^^^^-
   |         |                      |
   |         |                      borrowed value does not live long enough
   |         argument requires that `wrapped` is borrowed for `'im`
22 |     }
   |     - `wrapped` dropped here while still borrowed

which only makes sense if it were trying to .work_with_m<'im>() – but I'm not asking for that, or am I?

chrysn
  • 810
  • 6
  • 19
  • 2
    Does this answer your question? [Why do the lifetimes on a trait object passed as an argument require Higher Ranked Trait Bounds but a struct doesn't?](https://stackoverflow.com/questions/50946525/why-do-the-lifetimes-on-a-trait-object-passed-as-an-argument-require-higher-rank) In short, `'im` is chosen by the caller and cannot be shortened because *traits are invariant over their generic parameters*. – kmdreko Feb 07 '21 at 08:28
  • It does, thanks – as it took quite a bit of mind-bending to associate them, I'm summarizing here: The lifetime 'im is part of the requirement type, and thus becomes part of the concrete trait implementation that is passed in. Unlike for references that can be re-borrowed for a shorter time, it doesn't follow from the impl implementing `M<'im>` that it'd also implement `M<'x>` for any shorter `'x`, so it can't be put in there. (And to satisfy the child.work_with_m requirements, it'd have to implement M for a lifetime that's at most as long as that of wrapped). – chrysn Feb 07 '21 at 12:52
  • For follow-up, it looks like I manage to get rid of the original reason why the `M` trait needs a lifetime by using GATs (for the iterator associated with the trait), which are just starting to become viable with the [probably pending removal of the `incomplete_feature` marker](https://github.com/rust-lang/rust/issues/44265#issuecomment-775423925). In the concrete application, this means that [coap-message !2](https://gitlab.com/chrysn/coap-message/-/merge_requests/2) can be merged, and the coap-handler crate won't need to have generic lifetimes on its extraction function any more. – chrysn Feb 09 '21 at 10:22

0 Answers0