#1 can be desugared as &'c mut *(a.0), and according to the rules 2094-nll - The Rust RFC Book, the lifetime 'c must be constrained by some lifetimes(i.e. 'x: 'c), the supporting prefixes for this loan are *(a.0), a.0, and a, the lifetime 'x is determined by a, however, a is not a reference, it's a non-reference variable. So, I don't know how the lifetime constraint be determined in this case.
I suppose the variable a could be assumed to have the lifetime 'a, however, the following example has proven it's not.
fn show<'a>(i:&'a mut i32)->&'a mut i32{
let r;
{
let a = A(i);
r = &a;
}
r;
}
If a could have lifetime 'a, the example would be correctly compiler because 'a is valid everywhere in the function body, however, the compiler complains that a does not live long enough, which means a should have the lifetime associated with its block scope in which it is declared.
So, I wonder what the reborrow constraints are in the first example when the calculated ultimate supporting prefix is a non-reference variable.
In both of your examples, the variable a has type A<'a> (using the 'a defined in the function signature).
In the first example, return a.0, there's no reborrowing going on at all, and your desugaring is incorrect: a.0 has the type &'a mut i32, and you're moving the field out of a to return it.
In the other, however, you introduce a second, new lifetime on the line r = &a. This makes r have the type &'short A<'a>, and it's 'short that doesn't live long enough— It ends when the variable a goes out of scope at the end of the inner block.
That gets into how NLL works with place expressions, which I'm not familiar enough with to talk about in detail. But in essence, because a.0 has the type &'a mut i32 and there are no conflicting accesses of a.0 or i, the borrow checker can allow the lifetime of the reborrow to equal 'a.
This is still notably different from the non-compiling case, where you're storing a reference to a structure that will be dropped at the end of the scope.
a.0 is a &'a mut i, and is one of the supporting prefixes that gets deref'd.
Variable a's type (A<'_>) is constrained to at most 'a as per the assignment.
I don't understand what is "proved". Your failing example doesn't make sense at multiple levels, you're not returning anything and if you were, you can't coerce a &_ to a &mut _.
Did you just mean "a isn't type A<'a> because the type wasn't printed as A<'a> in the error"? If I had to guess that's because the lifetimes involved are distinct lifetime variables whose constraints must hold. Remember the compiler doesn't really assign some strict set of lifetimes everywhere, it just tries to prove the constraints necessary for soundness hold.
At any rate, error messages may give clues as to what the borrow checker does, but isn't really proof of anything. Their annotations aren't normative (and at least sometimes, erroneous). Certainly you can't derive why the compiling version works from this example that doesn't even return the correct type.
Field accesses don't result in a lifetime constraint. But you also had a &mut deref, and that does result in a lifetime constraint.
For the first question: how to determine the reborrowing constraint
I thought the reborrow constraint is always determined by the last supporting prefix path in the supporting prefixes. After reading your prior answers, I think it's my misunderstanding. The reborrow constraint is determined by all of the supporting prefixes that are DereferenceExpression (i.e. have the form * e), each lifetime taken from them constitutes the constraints of the reborrow, right?
For example:
let foo = Foo { ... };
let p: &'p mut Foo = &mut foo;
let q: &'q mut &'p mut Foo = &mut p;
let r: &'r mut Foo = &mut **q;
The supporting prefixes of **q are **q, *q, q, hence the supporting prefixes that determine the constraint in this example are **q, *q. So the constraints are taken from their operand, they are *q and q, whose have the lifetime 'p and 'q, hence the reborrow constraints are 'p:'r and 'q:'r, Am I right?
In my first example in this question, the supporting prefixes are *(a.0), a.0, and a but the supporting prefix that has the form *e is just *(a.0), so the reborrow constraint used here is 'a(taken from a.0).
Variable a's type (A<'_>) is constrained to at most 'a as per the assignment.
This is my confusion in the second example. I thought A(i) can at most have the lifetime 'a, so r = &a can borrow a reference from a that has the lifetime 'a and 'a is valid everywhere in the function body, r holding the reference can be used everywhere in the function body. In the whole process, I seem to ignore the lifetime of the variable declared by let a = A(i); itself, a can only be alive in the inner scope, so it cannot produce a reference that exceeds the lifetime of a itself. Is my now understanding right?
You can't borrow a local variable a: A<'a> for 'a (you can't create a &'a A<'a> from a) any more than you can create a &'static &'static str pointing at s in this example.
let s: &'static str = "";
let r: &'static &'static str = &s;
Borrowing a is not the same thing as borrowing *(a.0).
The place a goes away at the end of its scope. The place *(a.0), which has a &'a mut _ pointing at it with a lifetime 'a that came from elsewhere, doesn't go away until sometime after 'a which is greater than the function body.
'a is valid not only everywhere inside the function body, but for some indeterminate region beyond the function body. The function body is but a lower bound. You can't create borrows to locals longer than the function body.
What do you mean by "it cannot produce a reference" and by "the lifetime of a itself"?
You can reborrow *(a.0) for up to 'a, even though the variable a goes away at the end of the scope.
I meant, the variable a has its lifetime at best in the innermost block. Maybe a can make the misunderstanding. So, consider this changed one:
fn foo<'a>( i:&'a mut i32){
let r;
{
let b = A(i);
r = &b;
}
r;
}
which can be analyzed as:
'body:{
let r:&'larger A<'a>;
'b:{
let b = A::<'a>(i);
r = &'x b;
}
r; //'larger should keep valid here
}
The declared variable b can at best have the lifetime 'b, so the lifetime 'x at best is 'b, instead, r requires the lifetime 'larger, which is larger than what b can at most supply, which is the reason the compiler complains here, right?
So, I meant the lifetime of b is 'bb, which should satisfy 'b: 'bb.
But that's not the same thing as the scope of a. It is often also called a lifetime, but like the RFC author, I prefer a distinct term to distinguish the scope from Rust lifetimes -- those things we annotate with ' which the compiler analyzes with constraints, etc.
You could build a mental model around that, and what you wrote is a reasonable explanation of why that code must error.
But being technical about it,[1] that is not why the compiler complains because that is not the analysis the compiler performs. The compiler complains because going out of scope is a use of the variable; in this case it's a shallow write, and a shallow write of b is incompatible with 'x needing to be valid further down the program.
Lifetimes don't effect the scope of values, and the scope of values are not part of the 'x: 'y style constraints that the compiler uses in its analysis.
Perhaps it all makes sense in your mental model, but I can't answer questions about that.
given that this thread opened by referring to the NLL RFC ↩︎
I see. So, in terms of "lifetime", we only talk about these lifetime parameters. The scope of a variable cannot be called lifetimes.
Instead, we can say a lifetime is valid in some regions and the regions are the scopes, right? I remember you used the utterance in some answers. Even more, we can say the lifetime denotes a region since the NLL reference says so:
any loans whose region does not include P are killed;
The compiler complains because going out of scope is a use of the variable; in this case it's a shallow write, and a shallow write of b is incompatible with 'x needing to be valid further down the program.
when b is going out of its scope, the operation is StorageDead, which performs "shallow write" access to the lvalue b, and at that point, the loan ('x, shared, b) should be in the scope due to we use r outside, and the loan is the loan for the path b, so, the loan is the relevant borrowing, and such two operations are conflicting due to one of them is a write operation.
Yes, I know region can denote a non-contiguous scope. However, NLL does use "scope" to determine whether a loan is alive.
We can then represent the set of loans that are in scope at a particular point using a bit-set and do a standard forward data-flow propagation.
So, I think the lifetime parameter denotes a region and the region can be associated with scopes, merely, a region may consist of several scopes and they may not be contiguous. Otherwise, how do we determine whether a loan is in scope according to its region(i.e. its lifetime)?