I've realized that my understanding of the rules for mutating data that exists behind a shared reference is very confused when it comes to slices. I believe my confusion stems from thinking of the slice as an object in itself, vs thinking of its entries as objects in themselves, vs thinking of various sub-slices as objects in themselves (vs all three things).
To this end, I hacked together a small example to test my understanding, and according to MIRI something is definitely not right. Maybe someone can point me in the right direction.
In the code below, Foo<T>
is just an owned contiguous slice of data. A FooMut<T>
represents a mutable sub-slice of a Foo<T>
(the mutability is not the point here, in real life there'd be both a mutable and a non-mutable version, but I'm trying to limit the amount of code in the example). Creation of a FooMut<T>
directly from a Foo<T>
using Foo::mut_view
is governed by normal ownership rules, so only a single one can exist at any time. Nothing strange there. Instead of creating a FooMut<T>
directly from a Foo<T>
, one can alternatively create a DynMut<T>
using Foo::dyn_mut
. A DynMut<T>
allows handing out multiple FooMut<T>
to the same Foo<T>
as long as the sub-slices are non-overlapping (tracked at run-time, see DynMut::mut_view
for handing out sub-slices at runtime, and DynMut::reclaim
for giving them up).
use core::marker::{PhantomData};
use std::collections::{HashSet};
struct Foo<T> {
data: Box<[T]>
}
impl<T> Foo<T> {
fn dyn_mut<'a>(self: &'a mut Self) -> DynMut<'a, T> {
DynMut {
foo: self,
in_use: HashSet::new()
}
}
fn mut_view<'a>(self: &'a mut Self, offset: usize, len: usize) -> FooMut<'a, T> {
if offset + len > self.data.len() {
panic!("OOB")
}
if isize::try_from((offset + len)*core::mem::size_of::<T>()).is_err() {
panic!("Stated safety requirement for ptr::add violated")
}
FooMut {
data: self.data.as_mut().as_mut_ptr(),
offset: offset,
len: len,
phantom: PhantomData
}
}
fn new<V: Into<Box<[T]>>>(data: V) -> Self {
Self {
data: data.into()
}
}
}
struct DynMut<'a, T> {
foo: &'a mut Foo<T>,
in_use: HashSet<(usize, usize)>
}
impl<'a, T> DynMut<'a, T> {
fn reclaim<'b>(self: &'b mut Self, x: FooMut<'a, T>) {
let interval: (usize, usize) = (x.offset, x.offset + x.len);
self.in_use.remove(& interval);
}
fn mut_view<'b>(self: &'b mut Self, offset: usize, len: usize) -> Option<FooMut<'a, T>> {
if offset + len > self.foo.data.len() {
panic!("OOB")
}
if isize::try_from((offset + len)*core::mem::size_of::<T>()).is_err() {
panic!("Stated safety requirement for ptr::add violated")
}
let x: (usize, usize) = (offset, offset + len);
if (& self.in_use).into_iter().any(|y| (x.0 >= y.0 && x.0 <= y.1) || (x.1 >= y.0 && x.1 <= y.1)) {
None
}
else {
self.in_use.insert(x);
Some(FooMut {
data: self.foo.data.as_mut().as_mut_ptr(),
offset: offset,
len: len,
phantom: PhantomData
})
}
}
}
struct FooMut<'a, T> {
data: *mut T,
offset: usize,
len: usize,
phantom: PhantomData<&'a T>
}
impl<'a, T> FooMut<'a, T> {
fn mut_entry(self: & mut Self, i: usize) -> &'a mut T {
if i >= self.len {
panic!("OOB")
}
unsafe { &mut *(self.data.add(self.offset + i)) }
}
}
fn main() {
let v: Vec<u64> = (0..10).collect();
let mut foo: Foo<u64> = Foo::new(v);
let mut dynmut: DynMut<u64> = foo.dyn_mut();
let mut fdm1: FooMut<u64> = dynmut.mut_view(1, 3).unwrap();
let mut fdm2: FooMut<u64> = dynmut.mut_view(5, 3).unwrap();
*fdm1.mut_entry(1) = 42;
*fdm2.mut_entry(1) = 4242;
let mut foomut: FooMut<u64> = foo.mut_view(0, 10);
for i in 0..10 {
println!("{}", foomut.mut_entry(i));
}
}
I'm guessing I need to protect some data with an UnsafeCell
, but I'm not sure which. The entire contents of Foo<T>
's data
, or each individual entry? Or am I completely misunderstanding something else here?