Why does a longer lifetime in a reference that refers to a reference with shorter lifetime is permitted in item?

fn show<'x:'b,'b>(v:&'x &'b i32) -> &'x i32{
     *v
}
fn main(){
}

This item in the code can be compiled, however, a reference with a longer lifetime that refers to a reference with a shorter lifetime can result in using a dangling reference, since &'b i32 cannot be valid everywhere in 'x. Moreover, we seem not to be permitted to form the value that can be passed to v. Why the definition of the item is permitted?

Updated:

*v has the type &'b i32, which is a supertype of &'x i32 because 'x: 'b, hence, *v cannot be coerced to &'x i32,anyway this example is compiled.

It compiles just fine.

'x: 'b does not mean that x is longer than b. It means x is longer than or equal to b. Then, two other facts are:

  1. the equality case (resulting in &'b &'b T) is fine;
  2. References are covariant in their lifetime parameter, so &'long T coerces to &'short T.

As for why this is permitted: I guess it's a convenience feature. I'm not entirely sure, but I think that without this behavior, a lot of generic code would need a lot more explicit lifetime annotations. Actually, it's because of the implicit &'b i32: 'x bound, see Steffahn's answer below.

2 Likes

See the updated part.

To add to the previous response: The type &'x T indeed comes with an implicit T: 'x constraint, so in this example &'x &'b i32 creates an implicit &'b i32: 'x constraints, i.e. 'b: 'x. Together with the 'x: 'b bound that’s explicitly on the function, the function thus requires both lifetime arguments to be equal, and that’s okay in general, e.g. you can write fn foo<'a, 'b>() where 'a: 'b, 'b: 'a {} andn it compiles fine, even though it’s not all that useful to use two lifetime parameters in the first place.

The function thus is essentially equivalent to

fn show<'x>(v: &'x &'x i32) {
}

but this function can still be called. The covariance of shared references in both their type argument and their lifetime argument means that the type &'s &'l i32 is covariant in both lifetime parameters, in particular also in 'l. Thus if you originally create any value of type &&i32, i.e. of type &'s &'l i32 where 's might be a shorter lifetime and 'l might be a longer lifetime, i.e. you’ll have 'l: 's of course, then the above-mentioned covariance means that the reference of type &'s &'l i32 can be coerced into type &'s &'s i32 and then passed to show.

2 Likes

Regarding the update question: As explained before, the explicit and implicit outlives-bounds make it so that the function show requires 'x and 'b to be the same lifetime. Thus it is unsurprising that the code compiles fine, as something like this:

fn show<'x>(v: &'x &'x i32) -> &'x i32 {
     *v
}

would compile fine, too.


All that’s really needed for the updated function to compile fine though is the (implicit) 'b: 'x constraint, so that the &'b i32 behind the reference can become a &'x i32. Thus something like

fn show<'x, 'b>(v: &'x &'b i32) -> &'x i32 {
     *v
}

compiles just fine, too.

Of course, the most flexible/useful (to the caller) version of this function would return a &'b i32 instead.

While we’re on the topic of references-to-references, the analogous situation of mutable references is also interesting in that while

fn foo<'x, 'b>(v: &'x mut &'b mut i32) -> &'x mut i32 {
     *v
}

compiles fine,

fn foo<'x, 'b>(v: &'x mut &'b mut i32) -> &'b mut i32 {
     *v
}

doesn’t, unlike the analogue code for immutable references; but why that’s the case is a separate discussion :slight_smile:

2 Likes

I would say:

  • for the question on the title, you will never have a long-lived reference refer to a short one, since the borrowing rule forbids that.

    (Rule 2) References must always be valid.

  • for the question in your description, 'x:'b,'b are not lifetimes of real references since they first of all are lifetime annotations/constrains/contracts that will be resolved by the compiler. Your code passes due to the resolution found by the compiler (i.e. 'a and 'b are allowed to be the same here), which is unsurprising at all.

I thought the explicitly specified bound would overwrite the implicit one. The truth is the item should have both of them. That means 'x equals to 'b if both are satisfied.

For this case

fn foo<'x, 'b>(v: &'x mut &'b mut i32) -> &'x mut i32 {
     *v
}

According to the implicit trait bound, 'b :'x, and *v can be desugared to &'c mut **v where 'c has the constraint: 'x : 'c, since the destination type is &'x mut i32 and the source type is &'c mut i32, so 'c must be 'x, which won' break the trait bound 'x : 'c. If 'c was not 'x, then &'c mut i32 would be a supertype of &'x mut i32, there's no such coercion conversion.

For the second case

fn foo<'x, 'b>(v: &'x mut &'b mut i32) -> &'b mut i32 {
     *v
}

Similar to above, the *v has the type &'c mut i32 where 'x: 'c and 'c can equal to 'x, the confusion is that 'x can also equal to 'b with the trait bound 'b:'x, I don't know why this example cannot be compiled. But, I know 'b is invariant in this example, anyway this is irrelevant in the analysis.


Incidentally, this example is not fine:

fn show<'a,'b>(v1:&'a mut i32, mut v2:&'b mut i32) where 'b:'a{
    v2 = v1
}

This exposes that a supertype cannot be coerced to a subtype, that is to say, the first example is ok if and only if 'c == 'x.

This's like saying the following should be compiled because T can be equal to V.

fn foo<T, V>(t: T) -> V { t } 

The implementation should be valid for any valid input type satisfying the constraints, not for some specific types.

2 Likes

Under your inspiration, I seem to understand the example

fn foo<'x, 'b>(v: &'x mut &'b mut i32) -> &'x mut i32   // 'b : 'c
{
     *v 
//   |
//   v
// 1. desugar to &'c **v, this means `'x : 'c`
// 2. in the function body, we are permitted to create a reborrow with lifetime `'x` such that `'x: 'x` is true.
}

For the second case, the point is we cannot assume 'b is 'x in the signature of the function, we can at most create a reborrow &'x mut i32, which can't be coerced to &'b mut i32.

Given the third case:

fn foo<'x, 'b>(v: &'x  &'b i32) -> &'b i32 {
	*v
}

The constraint for the reborrow is 'b : 'c, so we can create a reborrow &'b i32 with 'c is 'b such that 'b:'c is still true.

That's one way to think about it (i.e. &**v), but alternatively you're just returning a copy of the &'b i32 (i.e. *v) and you don't have to think about reborrowing.

1 Like

There's no possibility for overriding, because they constrain different things at different places. T: 'a means that you are allowed to hold on to a value of type T for at least the lifetime 'a. Meanwhile, &'a T means that the reference is valid for at least 'a. This requires T: 'a to be sound, but it is not the same constraint.

Yes, absolutely. However, thinking about reborrowing is an effective way to interpret this example

fn foo<'x, 'b>(v: &'x mut &'b mut i32) -> &'b mut i32 {
     *v
}

*v is desugared to &'c mut **v, so the constraints that will impose on 'c should be 'x: 'c, in this case we at most let 'c be 'x, so we at best can create a borrowing that has type &'x mut i32, which is not a subtype of or is &'b mut i32, so there is no coercion between them.

1 Like

True, the mutable case has to be a reborrow and not a copy.

2 Likes

But my thought experiment has a drawback, why the invented lifetime 'c that satisfies 'b : 'c can be promoted to 'b as long as provide that after which 'b :'c is still true, however, in this case

fn foo<'x, 'b>(v: &'x  &'b i32) -> &'b i32 {
	*v
}

The lifetime 'x satisfy 'b : 'x but 'x cannot be promoted to 'b even though 'b : 'x if 'x was 'b. The only interpretation I think is the function signature should provide &'x &'b i32 can accept any case where 'b : 'x. What do you think?

Yes, the function signature is an API that is enforced both for callers and for the function body, and the API here says that 'b: 'x (implicitly) but also that 'b and 'x may be distinct. In the case with a &'b mut i32, this matters because you must reborrow with the constraints

  • 'b: 'c
  • 'x: 'c

And altogether with the implicit function signature bound you have

  • 'b: 'x: 'c

And any lifetime "between" a distinct 'b and 'x will satisfy 'b: 'c but not 'x: 'c. The compiler suggestion to add 'x: 'b effectively makes 'x == 'b and there are then no lifetimes "between" 'b and 'x (but a better suggestion would be to return &'x i32).


However with a shared &'b i32 as in your latest code snippet, you don't get the 'x: 'c constraint for reborrows (supported prefixes stop at a shared deref, if you prefer) and you can copy the &'b i32 out from behind the outer &'x or &'x mut; either way you think about it, you can get a 'c == 'b without violating any constraints or the function API.

The function still has to accept distinct 'b and 'x as per the API. This has no real practical effect with &'x &'b i32, but even with &'x mut &'b i32 (where you can still return &'b i32) this is important as it allows the following.

    let mut v = vec![];
    v.push(foo(&mut b));
    v.push(foo(&mut b));
    v.push(foo(&mut b));

If you had 'x == 'b, b would be exclusively borrowed after first push for so long as v is alive.

1 Like

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.