What benefit the second lifetime ('o) provides? fn foo<'r, 'o: 'r>(arg: &'r Arg) -> Ret<'o>

Hello!

Consider this code (source):

pub trait Responder<'r, 'o: 'r> {
    fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o>;
}

I struggle to understand why would you ever need the lifetime 'o.

Lifetime 'o is the same as lifetime 'r or it outlives it, as declared in 'o: 'r.
So everything in response::Result<'o> live as long as 'r (that is, as long as varaible referenced by request), and maybe a little longer (if 'o != 'r).

But doesn't response::Result<'r> would mean the same? Specifying 'r lifetime for response::Result would mean that everything inside of it lives as long as Request object, or longer, because a lifetime specifier doesn't set an upper bound on lifetime, only a lower bound.

Can you explain why would anybody want to have two lifetimes, as described above? Would I lose anything if I instead used only one lifetime 'r, replacing 'o with 'r ?

Thanks!

The doc for that trait says it is to support a 'static lifetime, which can be longer than 'r:

///     This includes borrows from the `Request` itself (where `'o` would be
///     `'r` as in `impl<'r> Responder<'r, 'r>`) as well as `'static` data
///     (where `'o` would be `'static` as in `impl<'r> Responder<'r, 'static>`).

This is not a problem as long as you don't want to use a `static lifetime for the result.

1 Like

But even with a single lifetime I still can return a type with 'static lifetime: playground

So it doesn't seem like I lose the ability to return 'static lifetime.

The problem in that example is that the expression &Request will produce a &'static Request, since it's a literal that can be a constant and is subject to promotion. Borrowing a variable instead fails to compile:

    let mytype = MyType;
    let request = Request;
    let result: MyResult<'static> = mytype.respond_to(&request);
2 Likes

Since you've already chosen a solution, feel free to ignore this.


As best I can tell, you were thinking of this from the point of view of the implementing type. The requirement to pick a single lifetime 'o for the return type Result<'o> where 'o: 'r isn't any more stringent than saying "always return Result<'r>", because the implementing type can always just choose to implement for 'r = 'o only.[1]

But from the point of view of the trait consumer, a signature like

pub trait Responder<'r> {
    fn respond_to(self, request: &'r Request<'_>) -> Result<'r>;
}

would mean you can only get a Result<'static> if you can pass in a &'static Request<'_>. And relatedly, you could only get out a Result<'lifetime_from_self> if you had a &'lifetime_from_self Request<'_>.

Just because the implementation may have returned something that had a longer lifetime which got coerced down to 'r doesn't mean that the caller can require that (nor could they "uncoerce" the lifetime back up in any case).

Ultimately the API is the way it is to support different signatures like so:

    fn respond_to(self, request: &'r Request<'_>) -> Result<'r>;
    fn respond_to(self, request: &'r Request<'_>) -> Result<'static>;
    fn respond_to(self: &'s S, request: &'r Request<'_>) -> Result<'s>
        where 's: 'r;

And the consumer of the trait chooses the signature they want by writing out the corresponding trait bound.

R: Responder<'r, 'r>
R: Responder<'r, 'static>
R: Responder<'r, 's> where 's: 'r

That said, to be honest, the 'o: 'r bound on the parameters is odd. It's not implied, so you have to repeat it everywhere, but it also serves no purpose to the trait.[2] If it wasn't present, implementations could choose to impose that limitation on their implementations if needed, or choose not to.

The advice for lifetimes (and some of the implementations) are also weird. The 'static implementations should be generic over 'o, so that it can meet all of the bounds mentioned above. And similarly for the self-borrowing implementations, assuming they are covariant, so that they can meet the Responder<'r, 'r> bound.

I don't know Rocket, so maybe it doesn't matter in practice, but it seems like a lot of unnecessary restrictions for consumers of the trait.


  1. It's also not more stringent for those who want to return Result<'static> due to covariance. ↩︎

  2. Okay, technically it does provide forward compatibility with a default method that requires the bound (but that seems unlikely). ↩︎

3 Likes

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.