let mut owned = vec![1, 2];
let x = &mut owned[0];
let y = &mut owned[1];
That this fails might come as a surprise to a lot of folks because it's easy for a human to see that it's perfectly safe. Namely, neither `x` nor `y` refer to the same point in memory, but the borrow checker still forbids it.What's the solution to this problem? Well, it depends on what problem you're trying to solve. One popular approach is to change the representation of your reference from a pointer to an index:
let mut owned = vec![1, 2];
let ix = 0;
let iy = 1;
Now, in order to dereference your reference, you need to do `&mut owned[ix]` instead of simply `{STAR}x`. There are various trade offs to this choice of course, including the fact that `&mut owned[ix]` will likely be bounds checked. (Bounds checking could be turned off by using `unsafe { owned.get_unchecked_mut(ix) }`.)But there are more solutions to this problem! Here's another:
use std::cell::Cell;
let owned = vec![Cell::new(1), Cell::new(2)];
let x = &owned[0];
let y = &owned[1];
x.set(10);
y.set(20);
println!("{:?}", owned);
This uses a concept known as "interior mutability" to permit mutating through a borrowed reference. `Cell` only works for `Copy` types ("plain old data"), whereas you'd need to use `RefCell` for non-copy types like `Vec<T>` or `String`.Finally, we can come full circle and use the `split_at_mut` API on slices to get two mutable views into the same slice. This is a good example of something that is ultimately implemented using `unsafe`, but exposes a `safe` interface:
let mut owned = vec![1, 2];
{
let (xs, ys) = owned.split_at_mut(1);
let x = &mut xs[0];
let y = &mut ys[0];
*x = 10;
*y = 20;
// The added scope is used so that the mutable
// borrow of `owned` is done before borrowing it
// again to print its contents below.
}
println!("{:?}", owned);
There are various trade offs to each of these approaches and which one you use depends on the problem you're trying to solve. Of course, the criticism of "I just want to use mutable pointers dammit" is valid, because Rust will, for the most part, force you to think of another way of solving your problem (unless you're OK using `unsafe` and raw pointers). We occasionally pay a price for it when the borrow checker isn't quite smart enough. In the example I've shown in this comment, it's obvious what the borrow checker should do, but in a real program, it's rarely so simple.