If you are new to Rust, one borrow checker-related issue you may run into is trying to implement an O(log N) algorithm that looks like this:
for i in 0..myvec.len() {
for j in (i + 1)..myvec.len() {
let iv = &mut myvec[i];
let jv = &mut myvec[j];
}
}
This can happen if you are trying to implement an object-to-object collision algorithm in a game engine, for example. In such case, you want to iterate all objects and check if they collider with all other objects. Solving the collision may require moving BOTH objects, which means modifying BOTH of them.
However, Rust's borrow checker seemingly won't let you do any of that. But why?
Fundamentally, Rust doesn't let you have two mutable references to the same object. This can turn good practice into inviolable rule.
For example, if you iterate a Vec by borrowing it with for element in &myvec, then you can't push elements into myvec. That's because if you push an element, and the number of elements is greater than the capacity of the vector, the vector will need to allocate a new internal array. This new internal array may have a different memory address. Which means the reference that you borrowed may point to the wrong memory address while you are inside a loop. This is something that is far more obvious to realize if you think about removing elements instead. If you started iterating a vector, and then you removed all elements of the vector while you are still inside the loop, then the element you are holding would have been destroyed in other languages like C++. In Rust, the borrow checker prevents these circumstances from arising in first place.
But what if you need to mutate the array while you iterate it? Then you can't borrow it. You need to iterate the indexes with for i in 0...myvec.len() such that you can stop borrowing an element before you start modifying the array inside the loop.
We are already doing that in our O(log N) loop, but the borrow checker still won't let us work in peace.
Indeed, logically speaking, it's impossible for that code to run into any memory-related errors. However, Rust's borrow checker isn't smart enough to figure that out. In particular, when the borrow checker sees this piece of code:
let iv = &mut myvec[i];
let jv = &mut myvec[j];
It can't guarantee that iv and jv don't point to the same memory address.
In other words, Rust's borrow checker is considering the possibility that i == j, in which case we have borrowed the exact same element twice. Of course, this can't actually happen in the loop we wrote, but that's the logic behind the issue.
To fix this borrow issue, we have to use split_at_mut or its immutable sibling split_at. What this function does is return the vector "split" at a certain index, as a tuple of two items: the first item of the tuple contains the elements before the index, while the second item of the tuple contains the elements starting from the index.
This means that these two "slices" of the vector do not overlap. Any index that we borrow from the first slice is guaranteed to not be the same memory as any index from the second slice.
myvec
|0|1|2|3|4|5|6|7| <- values
0 1 2 3 4 5 6 7 <- indexes
split_at(3)
left right
|0|1| |2|3|4|5|6|7|
0 1 0 1 2 3 4 5
This function doesn't create a copy of the vector, so there is no cost in using it.
for i in 0..myvec.len() {
// split at the first J index
let (left, right) = myvec.split_at_mut(i + 1);
// borrow from left
let iv = &mut left[i];
for j in 0...right.len() {
// borrow from right
let jv = &mut right[j];
}
}
In the code above, we are borrowing from my_vec once with split_at_mut, then we borrow once from left, and we borrow once from right.