In this example #1 is OK but #2 causes an error. This is a bit weird. The only difference between #1 and #2 is the order of producing borrowing. I think it may be subtle in 2094-nll - The Rust RFC Book. However, I don't know if my understanding is right, here.
For case #1, the borrowing & *(*this).a produce a loan ('a, shared, *(*this).a), and consider the borrowing & mut *this produces an action that is a deep write to *this, the former loan is not a relevant borrowing.
For case #2, the borrowing & mut *this that would be an action at #1 instead becomes the loan ('a, mut, *this), whereas the borrowing & *(*this).a that would be the loan at #1 instead becomes the action at #2, the action is a deep read to *(*this).a, and *this is a prefix of *(*this).a according to
For deep accesses to the path lvalue, we consider borrows relevant if they meet one of the following criteria:
[...]
there is a loan for some prefix of the path lvalue;
Hence, the second case has an error. Is this the correct interpretation of the example? The borrowing checker is sensitive to the order of loans?
For both copying and reborrowing, you need read access, and you don't have that while a mut reference exists. Once the &u8 is copied or reborrowed, it is disconnected from this, so getting a mut reference after is allowed. And I guess tuples make all their members exist at once (i.e. there is no NLL within a single tuple).
struct T { i: i32 }
fn example(this: &mut T) {
let x = this.i;
let y = &mut *this;
(x, y);
let x = &mut *this;
let y = this.i;
(x, y);
}
The first tuple is ok. We copied out a value from *this, and then we took an exclusive reborrow of *this. Then we used the copied thing, and the exclusive reborrow. No problem.
The second tuple is not ok. We took an exclusive reborrow of *this, and copied out a value from *this (but not through the exclusive reborrow), and then tried to use the exclusive reborrow (and the copied thing). So the exclusive reborrow must be active while we tried to copy out of *this, and that's a borrow error.
Do you find this weird?
The reborrowing of shared references has similar logic, because shared references implement Copy.
Your simplified example is a more intuitive interpretation. If we don't consider whether a type is Copy or something else, we just start with whether borrowing and action conflicts, is my understanding right?
Moreover, the control flow seems to follow the specified order of initialization? Is it true for struct?
The example you mentioned here is about Two-phase-borrowing. I think this is irrelevant here. I asked how the control flow that determines an action is associated with the corresponding initialization here.
The supporting prefixes for an lvalue are formed by stripping away fields and derefs, except that we stop when we reach the deref of a shared reference. Inituitively, shared references are different because they are Copy – and hence one could always copy the shared reference into a temporary and get an equivalent path.
And that definition is used when seeing if a deep access conflicts.
You're right that the order of borrows matters. (The particulars in your OP aren't exactly right (the loans don't have to be for 'a if nothing else), but I haven't gone through the time to walk through everything.)
It's generally left-to-right, top to bottom. There are some weird or suprising corner cases though. I'd call two-phrase borrows one of those corner cases. The note about primitives here is another one.