Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the borrow checker not understand that borrowing different parts of a slice is fundamentally okay?

The split_at_mut function (for splitting mutable slices at an index) requires unsafe code (according to the Rust book) to be implemented. But the book also says: "Borrowing different parts of a slice is fundamentally okay because the two slices aren’t overlapping".

My question: Why does the borrow checker not understand that borrowing different parts of a slice is fundamentally okay? (Is there a reason preventing the borrow checker from understanding this rule or is it just that it has not been implemented for some reason?)

Trying the suggested code with Rust 1.48 still results in the error shown in the book:

fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();

    assert!(mid <= len);

    (&mut slice[..mid], &mut slice[mid..])
}

fn main() {
    let mut vector = vec![1, 2, 3, 4, 5, 6];
    let (left, right) = split_at_mut(&mut vector, 3);
    println!("{:?}, {:?}", left, right);
}

Gives error message:

error[E0499]: cannot borrow `*slice` as mutable more than once at a time
 --> src/main.rs:6:30
  |
1 | fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
  |                        - let's call the lifetime of this reference `'1`
...
6 |     (&mut slice[..mid], &mut slice[mid..])
  |     -------------------------^^^^^--------
  |     |     |                  |
  |     |     |                  second mutable borrow occurs here
  |     |     first mutable borrow occurs here
  |     returning this value requires that `*slice` is borrowed for `'1`
like image 394
hkBst Avatar asked Feb 22 '21 10:02

hkBst


2 Answers

Why does the borrow checker not understand that borrowing different parts of a slice is fundamentally okay?

Because it's impossible to do generally. Sure, in this case it's obvious that &slice[..mid] and &slice[mid..] are disjoint, but the complexity skyrockets once you escape trivial cases and it quickly becomes impossible.

These special trivial cases aren't implemented specially because:

  1. Having a language feature that allows obviously disjoint slice borrows but not anything even slightly more complicated is unintuitive and can make beginners think they can do more things than they actually can
  2. These special cases really only boil down to a couple patterns, most of which can be accomplished with split_at and its mutable counterpart
  3. There's no way to reliably extend this to anything with the Index trait, which means that (&slice[..mid], &slice[mid..]) would be valid but (&vec[..mid], &vec[mid..]) wouldn't be, which is even more inconsistent. Of course this could be solved by making Vec a language intrinsic, but then what about VecDeque or user-defined data structures? It just leads to too much inconsistency that spirals into more inconsistency, which is something that Rust wants to avoid.
like image 140
Aplet123 Avatar answered Nov 08 '22 19:11

Aplet123


Rust’s borrow checker can’t understand that you’re borrowing different parts of the slice; it only knows that you’re borrowing from the same slice twice. What if the two slices are overlapping? There's no way to grantee that their not. (If there is, it isn't implemented yet)

That's why unsafe exists: when you know for sure that there's no way that your code will produce an unexpected behavior despite the compiler not being able to grantee that.

like image 2
Fabián Montero Avatar answered Nov 08 '22 21:11

Fabián Montero