The mutual exclusion property of mutable references can be very limiting when
working with a composite structure. The borrow checker (a.k.a. borrowck)
understands some basic stuff, but will fall over pretty easily. It does
understand structs sufficiently to know that it's possible to borrow disjoint
fields of a struct simultaneously. So this works today:
#![allow(unused)]fnmain() {
structFoo {
a: i32,
b: i32,
c: i32,
}
letmut x = Foo {a: 0, b: 0, c: 0};
let a = &mut x.a;
let b = &mut x.b;
let c = &x.c;
*b += 1;
let c2 = &x.c;
*a += 10;
println!("{} {} {} {}", a, b, c, c2);
}
However borrowck doesn't understand arrays or slices in any way, so this doesn't
work:
#![allow(unused)]fnmain() {
letmut x = [1, 2, 3];
let a = &mut x[0];
let b = &mut x[1];
println!("{} {}", a, b);
}
error[E0499]: cannot borrow `x[..]` as mutable more than once at a time
--> src/lib.rs:4:18
|
3 | let a = &mut x[0];
| ---- first mutable borrow occurs here
4 | let b = &mut x[1];
| ^^^^ second mutable borrow occurs here
5 | println!("{} {}", a, b);
6 | }
| - first borrow ends here
error: aborting due to previous error
While it was plausible that borrowck could understand this simple case, it's
pretty clearly hopeless for borrowck to understand disjointness in general
container types like a tree, especially if distinct keys actually do map
to the same value.
In order to "teach" borrowck that what we're doing is ok, we need to drop down
to unsafe code. For instance, mutable slices expose a split_at_mut function
that consumes the slice and returns two mutable slices. One for everything to
the left of the index, and one for everything to the right. Intuitively we know
this is safe because the slices don't overlap, and therefore alias. However
the implementation requires some unsafety:
#![allow(unused)]fnmain() {
use std::slice::from_raw_parts_mut;
structFakeSlice<T>(T);
impl<T> FakeSlice<T> {
fnlen(&self) -> usize { unimplemented!() }
fnas_mut_ptr(&mutself) -> *mut T { unimplemented!() }
pubfnsplit_at_mut(&mutself, mid: usize) -> (&mut [T], &mut [T]) {
let len = self.len();
let ptr = self.as_mut_ptr();
unsafe {
assert!(mid <= len);
(from_raw_parts_mut(ptr, mid),
from_raw_parts_mut(ptr.add(mid), len - mid))
}
}
}
}
This is actually a bit subtle. So as to avoid ever making two &mut's to the
same value, we explicitly construct brand-new slices through raw pointers.
However more subtle is how iterators that yield mutable references work.
The iterator trait is defined as follows:
Given this definition, Self::Item has no connection to self. This means that
we can call next several times in a row, and hold onto all the results
concurrently. This is perfectly fine for by-value iterators, which have
exactly these semantics. It's also actually fine for shared references, as they
admit arbitrarily many references to the same thing (although the iterator needs
to be a separate object from the thing being shared).
But mutable references make this a mess. At first glance, they might seem
completely incompatible with this API, as it would produce multiple mutable
references to the same object!
However it actually does work, exactly because iterators are one-shot objects.
Everything an IterMut yields will be yielded at most once, so we don't
actually ever yield multiple mutable references to the same piece of data.
Perhaps surprisingly, mutable iterators don't require unsafe code to be
implemented for many types!
All of these are completely safe and work on stable Rust! This ultimately
falls out of the simple struct case we saw before: Rust understands that you
can safely split a mutable reference into subfields. We can then encode
permanently consuming a reference via Options (or in the case of slices,
replacing with an empty slice).