Computing mutable slices makes borrow checker complain

I've occasionally written code like this:

fn takes_slice_mut(_: &mut [f32]){}

fn main() {
    let mut numbers = vec![0.0, -1.0, 3.0, 2.0, 0.5];
    takes_slice_mut(&mut numbers[..numbers.len()-1]);
}

Only to get this error from the borrow checker

error[E0502]: cannot borrow `numbers` as immutable because it is also borrowed as mutable
 --> <source>:6:36
  |
6 |     takes_slice_mut(&mut numbers[..numbers.len()-1]);
  |                          ----------^^^^^^^---------
  |                          |         |
  |                          |         immutable borrow occurs here
  |                          mutable borrow occurs here
  |                          mutable borrow later used here

I usually fix it like this, but it looks silly coming from most other languages to extract an expression to a variable when it's only used once:

fn takes_slice_mut(_: &mut [f32]){}

fn main() {
    let mut numbers = vec![0.0, -1.0, 3.0, 2.0, 0.5];
    let len = numbers.len();
    takes_slice_mut(&mut numbers[..len-1]);
}

Does anybody know a nicer way to resolve this?
Why does this bother the borrow checker anyway? I would think len() would return and release the borrow before the range describing the slice is created, but it seems like rustc is analyzing borrows only at the granularity of statements, not expressions? Can/will this be fixed someday?

That is mostly correct, and yes, this is being worked on.

In the meantime, another workaround that doesn't involve creating a variable is split_last_mut().

2 Likes

Quick tip about code formatting, if you use three backticks

```
fn some_rust_code() {}
```

instead of indentation


    fn some_rust_code() {}

then you’ll get syntax highlighting in this forum, making your code easier to read for others:


fn takes_slice_mut(_: &mut [f32]) {}

fn main() {
    let mut numbers = vec![0.0, -1.0, 3.0, 2.0, 0.5];
    takes_slice_mut(&mut numbers[..numbers.len() - 1]);
}

vs

fn takes_slice_mut(_: &mut [f32]) {}

fn main() {
    let mut numbers = vec![0.0, -1.0, 3.0, 2.0, 0.5];
    takes_slice_mut(&mut numbers[..numbers.len() - 1]);
}
1 Like

There's a special case in the compiler that makes the borrow checker allow patterns like this, but it only applies to method call syntax. If you rewrite your code to use the index_mut method instead of x[i] syntax for indexing, it will compile:

use std::ops::IndexMut;

takes_slice_mut(numbers.index_mut(..numbers.len()-1));

For more explanation of why this requires special handling, see RFC 2025.

The RFC leaves open the possibility of expanding this special case to cover more types of code in the future, so it's possible that the compiler will eventually be able to compile your original code, too.

2 Likes

Thanks for linking to that RFC. I think it doesn't address the question as to why arguments aren't simply evaluated in the order that would make such common cases compile, i.e. self last. Is that because self is guaranteed to be evaluated first? (I know that for backwards compatibility reasons, argumenf evaluation order for free functions is quasi-guaranteed.)

This is discussed in the Alternatives section, under “Modifying the desugaring to evaluate receiver after arguments.”

1 Like