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; let y = slice; into
let x = slice; 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
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.
&[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.