if this is an assignment lv = <rvalue>, then any loan for some path P of which lv is a prefix is killed.
What does "loan is killed" mean? Does it mean we can never use v after the line? However, this is not true since the following example is valid
fn main(){
struct A(i32);
let mut i = A(0);
let mrf = & mut i;
let v = & mut (*mrf).0; // #0
mrf = ...; // #1
println!("{}",v); // #2
}
v can be used at #2 after the relevant loan at #0 is "killed" at #1. So, what does the "loan is killed" mean here? If it means, the loan won't be considered in the set of relevant borrows when doing borrowing checking?
However, this interpretation cannot apply to this case
fn main(){
struct A(i32);
let mut i = A(0);
let mrf = & mut i;
let v = & mut (*mrf).0;
let mut ii = A(1);
*mrf = ii; // error
println!("{}",v);
}
The wording says ** any loan for some path P of which *mrf is a prefix is killed.**, however, the loan is still considered in the scope when find out the relevant borrows for lvalue *mrf
It means that anything that borrows (even transitively) from v will be invalidated (so you won't be able to use them anymore, unless you overwrite them with a valid value). For example:
fn main() {
let mut a = 1;
let mut b = &a; // b borrows from a
a = 2; // This invalidates b
//println!("{}", b); // Error, b is not valid anymore
b = &a; // We assigned a new value to b, now it's valid again
println!("{}", b);
}
Note that this is a pretty simple example, in practice there may be no easy way to do the equivalent of b = &a to make it valid again.
Here I believe that v could either reborrow mrf or take ownership of it. In the first case it would borrow from mrf and keep mrf valid, while in the latter it would borrow from i but make mrf invalid. In this case it seems to move.
In this case v has to borrow mrf because mrf is used again later, and by writing through mrf it invalidates v.
Remember that loans are place-expression specific. It means (*mrf).0 is no longer borrowed. The borrow of i is still active though. And v can stay alive: it's a reborrow via(*mrf).0, but if mrf itself changes, the reborrow can still be valid (in this case, because i is still borrowed).
Reassigning &mut is one of the main motivation for separating the concepts of lifetimes and loans (problem case #4).
struct A(i32);
let mut i = A(0);
let mut mrf = & mut i; // loan of i for 'm
let v = & mut (*mrf).0; // loan of (*mrf).0 for 'v where 'm: 'v
let mut j = A(1);
mrf = &mut j; // kills loan of (*mrf).0 (loan of j for 'm)
println!("{}",v); // use of 'v (so 'm is also alive)
It kills the borrow of (*mrf).0 because now mrf points somewhere new.
If you use (say, overwrite) (*mrf).0 before the println!, things will still compile.
If you try to use i before the final println!, you'll get a borrow error.
I believe that is:
NB. In this phase, when there is an assignment, we always clear all loans that applied to the overwritten path; however, in some cases the assignment itself may be illegal due to those very loans. In our example, this would be the case if the type of list had been List<T> and not &mut List<T>. In such cases, errors will be reported by the next portion of the borrowck, described in the next section.
[...]
lvalue is a shallow prefix of the loan path
shallow prefixes are found by stripping away fields, but stop at any dereference
so: writing a path like a is illegal if a.b is borrowed
struct A(i32);
let mut i = A(0);
let mrf = & mut i; // loan of i for 'm
let v = & mut (*mrf).0; // loan of (*mrf).0 for 'v ('m: 'v)
let mut ii = A(1);
*mrf = ii; // kills loan of (*mrf).0
// But is also a shallow write of *mrf
// And *mrf is a shallow prefix of (*mrf).0
// Thus borrow error if 'm is still alive
println!("{}",v); // Use of 'v (so 'm is still alive)
Annotated assignment like that example implicitly reborrow (or upcast). Non-annotated versions move. I don't think the nll rfc goes into that distinction though.
The more complicated reborrowing scenarios are discussed in this section:
Since in this particular case the lifetime on the right is the result of a temporary that can't be used anywhere else, you could if you like consider the lifetimes to be the same:
let mrf:&/*'a*/ mut i32 = &/*'a*/ mut I;
In which case there are no constraints within the given code.
If you instead mean, what are the limits of 'a, it depends on your perspective.
One mental model is that 'a cannot exceed the liveness scope of I (or any other exclusive action upon I). The implicit perspective is that of a sound, compiling program.
Another is the perspective of what the borrow checker does, in which case 'a isn't constrained, it is instead calculated based on the uses of mrf and the rest of the code, as described in the RFC (as only the first step of the borrow checking process). Depending on the rest of the analysis, the program may or may not compile.
If you mean neither of these, I have no idea what you do mean.
struct A<'l>(&'a i32);
impl<'l> Drop for A<'l>{
fn drop(& mut self){}
}
fn main(){
let a;
let I = 0;
a = A(& /*'a*/ I); // #1
// #2
// #3
}
At #3, <A<'_> as Drop>::drop(&mut a) is effectively called, which is a use of 'a, so 'a is still alive there.
The loan of I exists for all of 'a (nothing kills it).
At #2, I goes out of scope, which is incompatible with the loan existing. That's the borrow checker error.
If you change the declaration order, a drops and there are no further uses of 'a; then I goes out of scope and there is no conflict (as nothing makes 'a / the loan of I to exist after the drop of a).
Since it has no destructor it should be a shallow write (StorageDead), but there's also no difference since the type has no fields and isn't a reference.
Another thing I may misunderstand is whether a borrow is is_read or is_write in this pseudo code:
fn access_legal(lvalue, is_shallow, is_read) {
let relevant_borrows = select_relevant_borrows(lvalue, is_shallow);
for borrow in relevant_borrows {
// shared borrows like `&x` still permit reads from `x` (but not writes)
if is_read && borrow.is_read { continue; }
// otherwise, report an error, because we have an access
// that conflicts with an in-scope borrow
report_error();
}
}
The comment seems to imply that if the relevant borrow is a shared borrow &x, the borrow.is_read is true, otherwise a mutable borrow & mut x is considered is_write, right?
let right = &self.right; // loan1: ('a, shared, (*self).right)
let this = &*self; // loan2: ('b, shared, *self)
// 'a lives here
println!("{right}");
The action at loan2 is a shallow read to lvalue *self, the relevant borrows will be
For shallow accesses to the path lvalue, we consider borrows relevant if they meet one of the following criteria:
lvalue is a shallow prefix of the loan path
the shallow prefix of (*self).right is (*self).right and *self, hence the relevant borrow is generated from loan2, both the relevant borrow and the action are read, so it's ok.
I don't know whether saying that the relevant borrowing is generated from a loan is accurate.
The rule says the relevant borrow if they meet one of the following criteria, however, the relevant wording in the list is using loan.
Maybe, it means a loan, for example ('a, shared/mut, lvalue), denotes a borrow &'a (mut) lvalue, right?