1

This code

// No particular meaning, just MVCE extracted from larger program
pub fn foo(mut v: Vec<i32>) {
    let x = &v[0];
    for _ in [0, 1] {
        if *x == 0 {
            v[0] = 0;
        }
    }
}

fails to compile with

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> <source>:5:13
  |
2 |     let x = &v[0];
  |              - immutable borrow occurs here
3 |     for _ in [0, 1] {
4 |         if *x == 0 {
  |            -- immutable borrow later used here
5 |             v[0] = 0;
  |             ^ mutable borrow occurs here

I would expect it to compile because lifetime of *x ends before mutable v[0] is created ?

Code compiles if I remove the for-loop:

    /*for _ in [0, 1]*/ {
        if *x == 0 {
            v[0] = 0;
        }
    }

What is the reason of such behavior ?

5
  • 1
    The compiler is just not smart enough to prove that the loop executes only once, which is required for the code to be sound. Maybe it's not even trying to, as normally the point of loops is to execute multiple times... Commented Jun 2 at 12:25
  • 1
    @user4815162342 I believe it's the second one: AFAIK the Rust compiler does not try tracking through a for (or while) loop at all (which is why e.g. they can't have a value in any situation). By comparison, it perform at least some flow analysis through loop, and in this case if the for is replaced by a loop and a break is added inside the conditional the code will compile: play.rust-lang.org/… Commented Jun 2 at 12:30
  • 1
    @Masklinn: while and for loops are desugared to loop, so the type of loop has no incidence on the borrow-checker; it's purely a matter of iterations. Commented Jun 2 at 12:35
  • Would be really happy to know the reason for dislikes. Commented Jun 2 at 17:45
  • 1
    @yugr: So would I... I wish downvotes required entering a reason (or picking an existing reason); they're just very crude as they are, providing no constructive feedback to the OP. Commented Jun 3 at 7:04

1 Answer 1

3

I would expect it to compile because lifetime of *x ends before mutable v[0] is created.

No it doesn't, due to the loop.

In the presence of a loop, the variables within the loop body must live across all iterations, not just the current iteration.

In this specific case, this means that x must be valid not only for the first iteration, but also the second. This keeps x alive throughout the loop body.

There's a special caveat carved in the liveness rules in the case of unconditionally replaced variables, that is, if you were to assign again to x before the loop exits it would work again:

pub fn foo(mut v: Vec<i32>) {
    let mut x = &v[0];
    for _ in [0, 1] {
        if *x == 0 {
            v[0] = 0;
        }
        
        x = &v[0];
    }
}

This time, the compiler can see that x need not be alive after *x == 0 (because it'll be replaced), and therefore ends the borrow of v after the valuation of the condition.

Sign up to request clarification or add additional context in comments.

3 Comments

There is no second iteration, which I assume is exactly why the author asks the question.
@Masklinn: I prefer not to guess what the author assumes, they did not indicate anywhere in their questions that they made such assumption, so I will stick to the question as asked.
@Masklinn yes, single iteration was just to simplify the code (and I agree that BC should work the same regardless of number of iterations). I've updated the question so that mistake is more obvious.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.