What is the default lifetime in the returned impl Future in this example?

use std::future::Future;
trait A<W>{
   fn foo<'b>(&'b self, _:W, buf:&'b [u8])->impl Future<Output = ()>;
}
struct B;
struct C;
impl<'a> A<&'a B> for  C{
   fn foo<'b>(&'b self, w:&'a B, buf:&'b [u8])->impl Future<Output = ()>{
       async move{
          w;
          buf;
      }
   }
}

This code can be compiled. However, if we impose the lifetime 'a to the returned Future

use std::future::Future;
trait A<W>{
   fn foo<'b>(&'b self, _:W, buf:&'b [u8])->impl Future<Output = ()>;
}
struct B;
struct C;
impl<'a> A<&'a B> for  C{
   fn foo<'b>(&'b self, w:&'a B, buf:&'b [u8])->impl Future<Output = ()> +'a{
       async move{
          w;
          buf;
      }
   }
}

It won't be compiled and the suggestion is that

lifetime may not live long enough
consider adding the following bound: 'b: 'a

However, this is not possible, adding this bound will make the signature mismatched. If imposing the lifetime 'b to the returned Future, there will be a similar error that and suggestion that:

lifetime may not live long enough
consider adding the following bound: 'a: 'b

So, I wonder what the default lifetime is imposed on the returned Future? It seems like a lifetime 'c such that 'a:'c and 'b:'c, however, it cannot be written explicitly in the code?

use the "Captures trick" works in this example, although a warning is given:

   fn foo<'b>(&'b self, w:&'a B, buf:&'b [u8])->impl Future<Output = ()> + Captures<& 'a()>{
       async move{
          w;
          buf;
      }
   }

Effectively, yes. The return type can rely on both lifetimes. You can't make the implementation more restrictive. You can make it less restrictive (e.g. + 'a and don't capture 'b). AKA "refined".

Let me try to flesh this out. This may be the closest thing I have to a citation. Also

  • I only reskimmed it, and
  • What stabilized isn't the same as the RFC (namely all lifetime parameters are in scope)

But basically the idea is, any generics you could name in the return type are "in scope"/"captured" by the return type, and it's as if you had a generic associated type

// The type can rely on all these input generics
// vvvvvvvvv      v           vv
<Implementor as A<W>>::Opaque<'b>: Future<Output = ()>
where
    // implied bounds from signature
    Self: 'b

And just like with non-opaque associated types, if all the input generics (including the implementor) meet some bound : 'c, the compiler knows the associated type also meets : 'c.

These fail because both 'a and 'b may be larger than the proposed lifetime 'c that all the generics can meet. Or in other words, W: 'b may not hold in the first example; 'a: 'b and 'b: 'a may not hold in the second example.

However, although no such named lifetime exists in your example, there's no reason you can't name it in bounds elsewhere:

fn check_1<'b, 'c, T: A<W>, W>(imp: &'b T, w: W, buf: &'b [u8])
where
    'b: 'c,
    W: 'c,
    // T: 'c  -- already implied by 'b: 'c and the signature
{
    // This compiles
    let _bx: Box<dyn Future<Output = ()> + 'c> = Box::new(imp.foo(w, buf));
}

The returned opaque doesn't have a "default lifetime" really, it's all about what constraints can be proven. Opaque: 'a and Opaque: 'b cannot be proven. But if 'b: 'c and W: 'c, then Opaque: 'c can be proven.

1 Like

Come to think of it, the same concept can be demonstrated without opaques.

1 Like