How to understand "the loan is killed"?

let list: &mut List<T> = ...;
let v = &mut (*list).value;
list = ...; // <-- assignment

The 2094-nll - The Rust RFC Book says

  • 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)

Kill the loan does not mean to invalidate the reference produced from the loan, right?

Right.

Ok. let me try to phrase what I understand from your answers:

  1. The killed loans before the action A that kills them won't be considered relevant borrowings when calculating for the actions after A.
  2. The borrowing checking still regulates the action that would kill some loans to check whether the action conflicts with some loans.
  3. "kill loan" does not mean kill the references borrowed from the loans.

In the NIL concept, Is the function called an lvalue? For example

let mrf = & mut *fun();

*fun(), fun() are lvalues(supporting prefixes), right?

Moreover, when the borrowing does not occur re-borrowing, what are the constraints imposed on the lifetime? For example:

let mut I = 0;
let mrf:&/*'a*/ mut i32 = &/*'a*/ mut I;

I think that's correct.

Yes (today we call them place expressions).

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:

https://rust-lang.github.io/rfcs/2094-nll.html#reborrow-constraints

The example is

let mut I = 0;
let mrf:&/*'a*/ mut i32 = &/*'a*/ mut I;  // #1

I is not borrowing but is a variable of type i32, I think that the reborrowing does not occur at #1, but how the lifetime is constrained here?

If you mean lifetime constraints, the only constraint based on the code given is that the lifetime on the right outlives the lifetime on the left.

let mrf:&/*'b*/ mut i32 = &/*'a*/ mut I;  // 'a: 'b

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.

I meant what the limits of 'a. From the perspective of what the borrow checker, I don't know how the checker processes this case

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
}

The line at #1 will report that the lifetime does not live long enough. How does the borrowing check process this case?

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).

1 Like

At #2, can it be considered as an action that deep writes to I

Dropping an lvalue LV. Dropping an lvalue can be treated as a DEEP WRITE, like a move, but this is overly conservative.

I don't know whether I(primitive type) is conceptually dropped at #2.

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?

Right (or uniq, but for the purposes of access_legal you can consider those the same as mut).

The fact that access is ok when both are read is why shared reborrowing and the like doesn't invalidate shared borrows.

1 Like

I see,

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?

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.