0

I've reduced my problem to the following code:

struct Struct<'a, 'b, T> {
    a: &'a T,
    b: &'b T,
}

trait Trait<'a, 'b, T> {
    fn a(&self) -> &'a T;
    fn b(&self) -> &'b T;
}

impl<'a, 'b, T> Trait<'a, 'b, T> for Struct<'a, 'b, T> {
    fn a(&self) -> &'a T {
        self.a
    }
    fn b(&self) -> &'b T {
        self.b
    }
}

struct Confused<T> {
    field: T,
}

impl<T> Confused<T> {
    fn foo<'a, 'b>(&'a self, param: &Struct<'a, 'b, T>) -> &'a T {
        param.b();
        param.a()
    }

    fn bar<'a, 'b, U: Trait<'a, 'b, T>>(&'a self, param: &U) -> &'a T {
        param.b();
        param.a()
    }
}

The function foo is okay, but when I replace the concrete type Struct<'a, 'b, T> with a generic type U: Trait<'a, 'b, T>, I get the following error:

error[E0309]: the parameter type `T` may not live long enough
  --> src/lib.rs:31:15
   |
24 | impl<T> Confused<T> {
   |      - help: consider adding an explicit lifetime bound `T: 'b`...
...
31 |         param.b();
   |               ^
   |
note: ...so that the reference type `&'b T` does not outlive the data it points at
  --> src/lib.rs:31:15
   |
31 |         param.b();
   |               ^

The suggestion to add the bound T: 'b doesn't make sense to me, since 'b is a parameter to bar(). How can I fix bar() to accept any implementation of Trait<'a, 'b, T> as a parameter?

Tavian Barnes
  • 12,477
  • 4
  • 45
  • 118

1 Answers1

3

When you write a generic type such as:

struct Foo<'a, T> {
    a: &'a T,
}

Rust automatically adds an implicit restriction of the type T: 'a, because your reference to T cannot live longer than T itself. This is automatic because your type would not work without it.

But when you do something like:

impl<T> Foo {
    fn bar<'a, 'b>() -> &'a T {/*...*/}
}

there is an automatic T: 'a but not a T: 'b because there is no &'b T anywhere.

The solution is to add those constraints by yourself. In your code it would be something like this:

impl<T> Confused<T> {
    fn bar<'a, 'b, U: Trait<'a, 'b, T>>(&'a self, param: &U) -> &'a T
    where
        T: 'b, //<--- here!
    {
        param.b();
        param.a()
    }
}

rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • So for `foo()`, the `T: 'b` constraint is implied by `Struct<'a, 'b, T>`. But the same doesn't happen for `U: Trait<'a, 'b, T>`, even though it would be impossible to implement that trait if that bound did not hold. – Tavian Barnes Mar 31 '20 at 15:29
  • @TavianBarnes: Yes, I think it is exactly that. The funny thing is that you cannot implement `Trait<`a, `b, T>` unless `T: 'a + 'b`, but you can _use_ the `Trait<`a, `b, T>` even though `T: !'b` as long as you do not call `Trait::b`. In your original code, if you remove the call to `param.b()` it will compile just fine. – rodrigo Mar 31 '20 at 15:41
  • It looks like this might be fixed by https://github.com/rust-lang/rfcs/pull/2089 which is not yet implemented. – Tavian Barnes Mar 31 '20 at 15:50
  • @TavianBarnes: I don't think this change applies to your code. Note that any automatic requirement added to your original code would be a breaking change, as currently your code would compile without the `param.b()`, but adding a `T: 'b` automaticall would break it. – rodrigo Mar 31 '20 at 16:26
  • What would the added implied bound break exactly? Just `{ param.a() }` would continue to compile, and my existing code would unbreak. There can't be any callers of `bar()` that would be broken by the new bound since all instances of `Trait` satisfy it already. – Tavian Barnes Mar 31 '20 at 17:02
  • @TavianBarnes: Hmmm, you may be right. I was thinking that if you remove the `param.b()` your code will compile and the `'b` will be unconstrained. A generic user of this function could call it with a different `'b`, and that user could break with the automatic constraint. But since this latter generic would be uninstantiable anyway... maybe it doesn't matter and should fail earlier. – rodrigo Mar 31 '20 at 19:21
  • It now occurs to me that just having an impl of that trait around *doesn't* imply `T: 'b`, because the impl could just `panic!()` or otherwise diverge. It's only safe to assume `T: 'b` after the method returns. – Tavian Barnes May 11 '20 at 18:55
  • OTOH, if I wrote `trait Trait<'a, 'b, T> where T: 'b`, implied bounds would do what I expected – Tavian Barnes May 11 '20 at 20:47