Some perspective if someone is unsure about the borrow checker and the interplay with the from_raw_parts_mut
function (like I still am, as a beginner).
The following code will compile without an error and probably run without doing anything unexpected, but is nonetheless invalid due to access of the same memory via different mutable references; see documentation for the specific wording. It is my understanding, that undefined behavior (UB) is a property of the semantics the language defines (or does not define, more accurately here) and the program loses all meaning, especially in the presence of a time traveling optimizer like LLVM.
This produces UB:
use std::mem::align_of;
fn main() {
let mut data: [u8; 4] = [0x01, 0x00, 0x00, 0x02];
let ptr = data.as_ptr() as *mut u32;
assert!(ptr.align_offset(align_of::<u32>()) == 0);
let my_ref: &mut [u32] = unsafe { std::slice::from_raw_parts_mut(ptr, data.len() / 4) };
data[0] = 200; // Borrowed here.
my_ref[0] = 5; // And independent borrow used here, but unseen -> UB
}
I'd like to imagine that the mutable reference my_ref
could be passed to a new thread, while the main thread works on data
directly and the rust borrow checker would let this happen since the lifetime and relationship of my_ref
is opaque, at least I think that's happening here. But the program is invalid no matter if any race conditions would occur or not due to UB.
A fix as it is indicated in the documentation of from_raw_parts
would be to make the lifetime relationship clear using a helper function to encapsulate the unsafe code.
The lifetime for the returned slice is inferred from its usage. To prevent
accidental misuse, it’s suggested to tie the lifetime to whichever
source lifetime is safe in the context, such as by providing a helper
function taking the lifetime of a host value for the slice, or by
explicit annotation.
Example fixed, in the sense that the compiler catches the UB:
use std::mem::align_of;
fn alias_u8_to_u32(some_ref: &mut [u8]) -> &mut [u32] {
let ptr = some_ref.as_ptr() as *mut u32;
assert!(ptr.align_offset(align_of::<u32>()) == 0);
unsafe { std::slice::from_raw_parts_mut(ptr, some_ref.len() / 4) }
}
fn main() {
let mut data: [u8; 4] = [0x01, 0x00, 0x00, 0x02];
let my_ref = alias_u8_to_u32(&mut data);
data[0] = 200;
my_ref[0] = 5;
}
Note, that the function alias_u8_to_u32
does not even need explicit lifetime annotations since the defaults are sufficient. It helpfully identifies the error(s) now:
error[E0503]: cannot use `data` because it was mutably borrowed
--> src\main.rs:10:5
|
9 | let my_ref = alias_u8_to_u32(&mut data);
| --------- `data` is borrowed here
10 | data[0] = 200;
| ^^^^^^^ use of borrowed `data`
11 | my_ref[0] = 5;
| --------- borrow later used here
error[E0506]: cannot assign to `data[_]` because it is borrowed
--> src\main.rs:10:5
|
9 | let my_ref = alias_u8_to_u32(&mut data);
| --------- `data[_]` is borrowed here
10 | data[0] = 200;
| ^^^^^^^^^^^^^ `data[_]` is assigned to here but it was already borrowed
11 | my_ref[0] = 5;
| --------- borrow later used here
Some errors have detailed explanations: E0503, E0506.
For more information about an error, try `rustc --explain E0503`.
Hopefully my understanding is accurate and this answer helps :)