Why is the prefix of an lvalue which is itself not the killed loan?

Consider this case

fn main() {
   let mut i = 0;
   let rf = & mut i;
   i = 1; // #1
   println!("{rf}");
}

I know this case is an error. I just want to talk about this case from the technique perspective of the 2094-nll - The Rust RFC Book, which says

For a statement at point P in the graph, we define the "transfer function" -- that is, which loans it brings into or out of scope -- as follows:

  • [...]
  • if this is an assignment lv = <rvalue>, then any loan for some path P of which lv is a prefix is killed.

Prefix:

Prefixes. We say that the prefixes of an lvalue are all the lvalues you get by stripping away fields and derefs. The prefixes of *a.b would be *a.b, a.b, and a.

The compiler emits an error that:

i is assigned to here but it was already borrowed

However, rf is not considered alive after the assignment because it is killed. So, why is rf a relevant borrowing at point #1?

If the killing a borrowing did not affect the relevant borrowing, the following case wouldn't be compiled

fn main() {
   let mut i = 0;
   let mut rf = & mut i;
   let rrf = & mut *rf;
   let mut s = 0;
   rf = & mut s;
   let d = & mut *rf;
   println!("{rrf}");
   println!("{d}");
}

So, how to correctly understand that rule? Should that rule be rephrased to

if this is an assignment lv = , then any loan for some path P of which lv is a prefix is killed, where P is not lv.

1 Like

That's certainly incorrect. You are printing it after the assignment, which entails reading (and immutably borrowing) it. That's clearly a use.

2 Likes

Yes, the use of rf should make the borrowing alive at that point. However, the rule says the borrowing is killed so that the borrowing is not considered relevant when there is a shallow write to i because rf is a borrowing to the i whose prefix is itself.

The two examples differ in a crucial detail – the first one (that does not compile) assigns to the referent of the reference, whereas the second one merely reassigns the reference so it points to a different value.

The next paragraph in fact talks about exactly this issue:

1 Like

So, the rule should be changed to

if this is an assignment lv = <rvalue> , then any loan for some path P of which lv is a prefix is killed, where P is not lv.

which can clarify the note. Otherwise, the "next section" still cannot interpret the note because the relevant borrowings are only those that are in-scope

As you can see, it works in two steps. First, we enumerate a set of in-scope borrows that are relevant to lvalue -- this set is affected by whether this is a "shallow" or "deep" action, as will be described shortly. Then, for each such borrow, we check if it conflicts with the action (i.e.,, if at least one of them is potentially writing), and, if so, we report an error.

A killed borrowing is not considered in the scope.

It's not killed until after the assignment. It's still in scope at the beginning of the assignment.

At the point of the marked assignment, the loan of (*list).value is in-scope, but it does not have to be considered in-scope afterwards.

[...]

For each of these kinds of actions, we will specify below the rules that determine when they are legal, given the set of loans L in scope at the start of the action.

2 Likes

Ok. So, an lvalue is indeed a prefix of itself? The distinct between my case and the following case

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

is that there is a relevant borrowing at the point of the assignment in my case while there is no relevant borrowing in the assignment point above.

For shallow access, bullet 1 and bullet 2 says

  • there is a loan for the path lvalue
  • there is a loan for some prefix of the path lvalue;

which means ('a, mut, i) is a relevant borrowing. For the above case, there is no loan for list according to the bullet 1, no loan for list(prefix of list) according to the bullet 2, and no loan for a path of which list is a shallow prefix according to bullet 3

lvalue is a shallow prefix of the loan path

I believe that's all correct. The second bullet point considers one direction of nesting and indirection and the third considers the opposite direction. The first is just for clarity I think (and overlaps with both directions).

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.