A compile error because of lifetime

The following simple codes can compile:

trait Get {
    type T<'a> where Self: 'a;
    fn get(&self);
}

fn test_fun<'b, T>(get_to: T)
where
    T: Get<T<'b> = T> + 'b,
{
    get_to.get();
}

But these can not:

trait Get {
    type T<'a> where Self: 'a;
    fn get(&self) -> Self::T<'_>; //this is the only defference from above
}

fn test_fun<'b, T>(get_to: T)
where
    T: Get<T<'b> = T> + 'b,
{
    get_to.get();
}

The only difference between the two versions comes from return value of method get from trait Get.
What makes the error happened?

Adding the return type restrains the call to Get::get's lifetime as 'b, since <T as Get>::T<'x> is only defined when 'x = 'b. And that 'b' may outlive test_fun's scope, where get_to lives, resulting the borrow outlives get_to.

Reminder: T: 'a does not means for any variable x: T, x outlives 'a. (Think all those short lived Strings, but we have String: 'static.)

1 Like

It's curious that this compiles:

 trait Get {
     type T<'a> where Self: 'a;
     fn get(&self) -> Self::T<'_>; //this is the only defference from above
 }
 
-fn test_fun<'b, T>(get_to: T)
+fn test_fun<T>(get_to: T)
 where
-    T: Get<T<'b> = T> + 'b,
+    for<'b> T: Get<T<'b> = T> + 'b,
 {
     get_to.get();
 }

(Playground)

1 Like

Because with HRTB, Get::get is free to choose any lifetime instead forced to choose `'b".

2 Likes

Thanks. Why when adding the return type change the result? How the action of adding the return type get matter on compiling process?

I think it's a limitation of GATs and the bounds checks. With the second version, the compiler is assuming that the 'b is relevant -- that you're going to want to call T::get with a &'b self so you get a T back out. (The output of T::get might not be T for other lifetimes.)

The function body as written doesn't technically need that to happen. This program could be accepted.

But if the function did need that to happen, it can't happen because the caller of test_fun chooses 'b, and they can only choose lifetimes that are longer than the function body, and thus you can never borrow a local like get_to for a caller-chosen lifetime like 'b. So the compiler's assumption results in the borrow error.


If you don't need to have T::get return a T, you should just drop the bound: T: Get.

If you do need that, you need it to happen for arbitrarily short lifetimes, and that's what HRTBs (higher-ranked trait bounds) are for: T: for<'x> Get<T<'x> = T>.

3 Likes

I left a comment on #106832.

1 Like

What can I do if I do need the second version:

//pseudo-code
fn test_fun<'u, T>(get_to: T)
where
    for<'l where 'u: 'l>
        T: Get<T<'l> = T> + 'l,
{
    get_to.get();
}

If it's ok to require T to not have a non-'static lifetime, you can

fn test_fun<T>(get_to: T)
where
    for<'l> T: Get<T<'l> = T>,
{
    get_to.get();
}

If you want T to not necessarily be 'static, though, this will probably give you an error due to GAT limitations:

note: due to current limitations in the borrow checker,
this implies a `'static` lifetime

You can sometimes work around this by moving the GAT to a GAT-emulating trait:

// `Limiter` is a defaulted type parameter that takes the place of the
// `where Self: 'a` bound you had on the GAT.  `Self: 'x` is implied
// by the presense of the `&'x Self`, as it's required for the reference
// to be well formed.
trait GetGat<'x, Limiter = &'x Self> {
    type T;
}

// So here for example it's really `impl<'x, 'y: 'x>`
// Or alternatively `where &'y f32: 'x`, `where Self: 'x`
impl<'x, 'y> GetGat<'x> for &'y f32 {
    type T = &'y f32;
}

// And here's it really `for <'any where T: 'any>`
// (which you cannot write directly in Rust!)
fn test_fun<T>(get_to: T)
where
    T: Get + for<'any> GetGat<'any, T = T>,
2 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.