Immutable borrow as argument for mutable borrow does not compile

db1bfbc
Opened by Frank van Heeswijk at 2021-02-03 16:41:01

I have the following piece of code which I expect to work but the borrow checker does not agree:

fn main() {
    let mut data = vec![1; 100];
    {
        let (left, right) = data.split_at_mut(data.len() / 2);
    }
    println!("{:?}", data);
}

Please note that the extra scope is in principle not related to the issue I'm describing, I'm just doing it such that I can use the println! macro to ensure that the compiler does not optimize the code away because I'm not using it thereafter. (I'm new to Rust so I don't know the exact rules)

The error I get is the following:

main.rs:4:47: 4:51 error: cannot borrow `data` as immutable because it is also borrowed as mutable [E0502]
main.rs:4         let (left, right) = data.split_at_mut(data.len() / 2);
                                                        ^~~~
main.rs:4:29: 4:33 note: previous borrow of `data` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `data` until the borrow ends
main.rs:4         let (left, right) = data.split_at_mut(data.len() / 2);
                                      ^~~~
main.rs:5:6: 5:6 note: previous borrow ends here
main.rs:3     {
main.rs:4         let (left, right) = data.split_at_mut(data.len() / 2);
main.rs:5     }
              ^

However what I would expect is the following:

  1. data.len() takes an immutable borrow.
  2. data.len() returns the immutable borrow and divides the obtained u32 by 2.
  3. data.split_at_mut() takes a mutable borrow.

It seems like the borrow checker currently evaluates the statement in the wrong order.

The following workaround is available, but it feels like this is a place where we should not have the need to (rightfully) fight the borrow checker.

fn main() {
    let mut data = vec![1; 100];
    {
        let len = data.len();
        let (left, right) = data.split_at_mut(len / 2);
    }
    println!("{:?}", data);
}
  1. I'm going to naively assume this is because data.split_at_mut(data.len()) is a singular expression and you could theoretically access the immutable reference of data (from data.len()) at the same time as the returned mutable reference, which would violate the borrowing rules. If that's the case it sort-of makes sense why this is the case.

    Dan at 2016-06-02 12:50:14

  2. I think this is being tracked at https://github.com/rust-lang/rfcs/issues/811.

    Andrew Paseltiner at 2016-06-02 13:55:51

  3. Yeah, non-lexical borrows should fix this case. Until then you can use unborrow.

    Alex Burka at 2016-06-02 14:40:14

  4. I'm not entirely sure that non-lexical borrows can fix this without being a breaking change. The above code desugars to:

    fn main() {
        let mut data = vec![1; 100];
        {
            let (left, right) = DerefMut::deref_mut(&mut data).split_at_mut(Deref::deref(&data).len() / 2);
        }
        println!("{:?}", data);
    }
    

    Steven Allen at 2016-06-02 17:46:17

  5. @Stebalien I think non-lexical borrowck should allow that code. len does not return a borrow, so the immutable borrow is over after the parameters are evaluated and before the split_at_mut call.

    Alex Burka at 2016-06-02 18:01:37

  6. If we deref in order, we could write code that could print out the following:

    * Mutably dereferenced! Mutations allowed until next immutable deref.
    * Immutably dereferenced! Mutations forbidden until next mutable deref.
    * Mutated!
    

    Alternatively, we could unborrow and reborrow:

    * Mutably dereferenced! Mutations allowed until next immutable deref.
    * Immutably dereferenced! Mutations forbidden until next mutable deref.
    * Mutably dereferenced! Mutations allowed until next immutable deref.
    * Mutated!
    

    Or borrow late:

    * Immutably dereferenced! Mutations forbidden until next mutable deref.
    * Mutably dereferenced! Mutations allowed until next immutable deref.
    * Mutated!
    

    However, that's also a breaking change.

    Of course, one could argue that the behavior here is unspecified...

    Steven Allen at 2016-06-02 18:13:37

  7. Oh, indeed I missed the first deref_mut. Perhaps unborrow will remain relevant! On Jun 2, 2016 2:14 PM, "Steven Allen" notifications@github.com wrote:

    If we deref in order, we could write code that could print out the following:

    • Mutably dereferenced! Mutations allowed until next immutable deref.
    • Immutably dereferenced! Mutations forbidden until next mutable deref.
    • Mutated!

    Alternatively, we could unborrow and reborrow:

    • Mutably dereferenced! Mutations allowed until next immutable deref.
    • Immutably dereferenced! Mutations forbidden until next mutable deref.
    • Mutably dereferenced! Mutations allowed until next immutable deref.
    • Mutated!

    Or borrow late:

    • Immutably dereferenced! Mutations forbidden until next mutable deref.
    • Mutably dereferenced! Mutations allowed until next immutable deref.
    • Mutated!

    However, that's also a breaking change.

    Of course, one could argue that the behavior here is unspecified...

    — You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/rust-lang/rust/issues/34035#issuecomment-223375654, or mute the thread https://github.com/notifications/unsubscribe/AAC3n3yJcTSvGkIcgqZKQneM_5CMjcEYks5qHx18gaJpZM4Iseiw .

    Alex Burka at 2016-06-02 18:29:32

  8. See this thread on the topic:

    https://internals.rust-lang.org/t/accepting-nested-method-calls-with-an-mut-self-receiver/4588

    Niko Matsakis at 2017-01-10 17:40:14

  9. Current output:

    error[E0502]: cannot borrow `data` as immutable because it is also borrowed as mutable
     --> src/main.rs:4:47
      |
    4 |         let (left, right) = data.split_at_mut(data.len() / 2);
      |                             ---- ------------ ^^^^ immutable borrow occurs here
      |                             |    |
      |                             |    mutable borrow later used by call
      |                             mutable borrow occurs here
    

    Esteban Kuber at 2019-05-11 02:03:35

  10. What is the current state of this issue? It's now Feb. 2021 and I'm still getting this error. Are there any plans or is and will be the only solution to outsource the needed parameters into a separate variable beforehand?

    I am relatively new to Rust and I don't think I fully understand the borrow checker yet, but this 'workaround' shouldn't be the way to go shouldn't it?

    Xenomojin at 2021-02-03 08:55:11

  11. @Xenomojin for a case like the one above, you must bind the value of data.len() (which is Copy) to a new binding (let len = data.len();) and use that in the call. This is necessary because the borrow checker cannot know for sure that the immutable borrow that .len() needs doesn't overlap with the mutable borrow split_at_mut needs. This is a case we want to improve in the future, but it will require subtle handling that isn't yet done.

    Esteban Kuber at 2021-02-03 16:17:34

  12. Thank you very much for your response @estebank, I guess I just need to get used to it. I mean... its not that much of a cost compared to knowing for sure there won't be anything unforeseen at run time, unlike some C code where I was spending hours and hours not getting forward at all. But I'm seeing forward to having handled this little bumpiness in an improved manner some day in the future.

    Xenomojin at 2021-02-03 16:41:01