In unsafe code, is it correct to have several mutable references (not pointers) to the same array, as long as they are not used to write to the same indices?
Context
I would like to yield several (distinct) mutable views of an underlying array, that I can modify from different threads.
If the disjoint parts are contiguous, this is trivial, by just calling split_at_mut
on the slice:
let mut v = [1, 2, 3, 4];
{
let (left, right) = v.split_at_mut(2);
left[0] = 5;
right[0] = 6;
}
assert!(v == [5, 2, 6, 4]);
But I also want to expose non-contiguous disjoint parts. For simplicity, let's say we want to retrieve a mutable "view" for even indices, and another mutable "view" for odd indices.
Contrary to split_at_mut()
, we could not retrieve two mutable references (we want a safe abstraction!), so we use two structure instances instead, exposing only mutable access to even (resp. odd) indices:
let data = &mut [0i32; 11];
let (mut even, mut odd) = split_fields(data);
// …
With some unsafe code, it is easy to get such a safe abstraction. Here is a possible implementation:
use std::marker::PhantomData;
struct SliceField<'a> {
ptr: *mut i32,
len: usize,
field: usize,
marker: PhantomData<&'a mut i32>,
}
impl SliceField<'_> {
fn inc(&mut self) {
unsafe {
for i in (self.field..self.len).step_by(2) {
*self.ptr.add(i) += 1;
}
}
}
fn dec(&mut self) {
unsafe {
for i in (self.field..self.len).step_by(2) {
*self.ptr.add(i) -= 1;
}
}
}
}
unsafe impl Send for SliceField<'_> {}
fn split_fields(array: &mut [i32]) -> (SliceField<'_>, SliceField<'_>) {
(
SliceField {
ptr: array.as_mut_ptr(),
len: array.len(),
field: 0,
marker: PhantomData,
},
SliceField {
ptr: array.as_mut_ptr(),
len: array.len(),
field: 1,
marker: PhantomData,
},
)
}
fn main() {
let data = &mut [0i32; 11];
{
let (mut even, mut odd) = split_fields(data);
rayon::join(|| even.inc(), || odd.dec());
}
// this prints [1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1]
println!("{:?}", data);
}
So far, so good.
Problem
However, accessing a raw pointer is far for convenient: contrary to slices, we cannot use operator []
or iterators.
unsafe {
for i in (self.field..self.len).step_by(2) {
*self.ptr.add(i) += 1;
}
}
The obvious idea is to convert locally the raw pointer to a slice in the unsafe implementation:
let slice = unsafe { slice::from_raw_parts_mut(self.ptr, self.len) };
Then we could, for example, rewrite our implementation in functional style:
slice.iter_mut().skip(self.field).step_by(2).for_each(|x| *x += 1);
For this sample, it may not worth it, but for more complex code, it could be far more convenient to use slices instead of raw pointers.
Question
Is this correct?
This obviously violates the borrowing rules: two threads may simultaneously hold a mutable reference to the very same memory location. However, they may never write to the same indices.
Mutable reference aliasing is not listed as an unsafe superpower, but the list is not presented as exhaustive.