I agree with everyone who said "yes".
&
means "target memory will not change". When using mmap
, it is your responsibility to guarantee that. Converting to &str
is but one example -- the compiler is also free to optimize code like let x = slice[0]; let y = slice[0];
into let x = slice[0]; let y = x;
. So, no matter whether you create an &str
, if you turn an mmap
'd file into a &[u8]
, mutating that file causes the program to have UB.
I have no idea how to make use of the fact that the mutation happens in a separate process. TBH I do not see an argument for why it makes this problem any less serious. mmap
is entirely uncharted territory as far as formal models go (the ones I have seen, anyway), but once two processes mmap
the same file, isn't that the same as two threads in a single process sharing some memory? And at that point we know very well that mutating memory where another thread holds a &[u8]
is UB.
So, the least we need to do is tell the compiler by adding some type that internally employs UnsafeCell
. For example, we could use &[Cell<u8>]
. Now at least modification in general is not UB any more.
However, the optimization I mentioned above is still valid in this case. Worse, the inverse optimization is still valid as well! You might be reading the u8
or Cell<u8>
once, but the compiler is free to turn that into two reads and assume they yield the same result! That is because the compiler can assume that you have no data races.
So maybe &[AtomicU8]
works, then? Maybe. As @adamreichold mentioned, this is territory where volatile
enters the picture, and it is pretty unclear to me whether non-volatile atomic accesses are "good enough" here. Notice that atomic does not "imply" volatile. For example, even with atomic variables, the compiler may turn let x = var.load(ordering); let y = var.load(ordering);
into let x = var.load(ordering); let y = x;
(but not vice versa, that is a key difference to non-atomic accesses). With volatile, it is not allowed to do this.
That said, I cannot think of any reason to actually use volatile
here, or even think of any case where adding volatile
removes UB. Adding volatile
helps control the exact accesses that hit the memory subsystem, but I do not think it is ever needed from a UB perspective. So, to the best of my knowledge, &[AtomicU8]
(and making every single access Relaxed
) is okay. It is also rather horrible to use, I assume.