I was casting a field of a struct to a *mut
pointer, so I declared that struct's reference as mutable. But then I started getting warnings that the mut
modifier is not required. That seemed weird to me, I'm clearly mutating something, yet declaring it as mut
is unnecessary? This leads me to this minimal example:
struct NonMut {
bytes: [u8; 5]
}
impl NonMut {
pub fn get_bytes(&self) -> &[u8] {
&self.bytes
}
}
fn potentially_mutate(ptr: *mut u8) {
unsafe { *ptr = 0 }
}
fn why(x: &NonMut) {
let ptr = x.get_bytes().as_ptr() as *mut u8;
potentially_mutate(ptr);
}
fn main() {
let x = NonMut { bytes: [1, 2, 3, 4, 5] };
println!("{}", x.get_bytes()[0]);
why(&x);
println!("{}", x.get_bytes()[0]);
}
1
0
Playground link
Obviously dereferencing the pointer requires unsafe code, but from the perspective of someone just writing their own app and deciding to call why
, perhaps defined in an external crate, this behaviour seems completely unexpected. You pass a clearly nonmutable reference somewhere, the borrow checker marks your code as correct, but under the hood there is a mutable reference created for that struct. This could cause multiple living mutable references being created to NonMut
without the user of why
ever knowing about that.
Is this behaviour as expected? Shouldn't casting to *mut
require the operand to be mut
in the first place? Is there a compelling reason as to why it is not required?
has type &mut &mut [u8]. &mut &mut [u8] is needed as &mut [u8] has a Write implementation. needs an argument of type &mut W, where W implements Write. The reference to a value of type W implementing Write needs to mutable, as we want to keep track of the actual writing position in the value of type W.
let t = *something; for a something of type &mut T would mean we try to get ownership (not just a borrow) for the value referenced by something. (Unless T implements Copy .) You can use *something to assign values to and call methods or maybe also other stuff, but you can't move the value out of it.
A cast e as U is valid if e has type T and T coerces to U. A cast e as U is also valid in any of the following cases: e is a C-like enum (with no data attached to the variants), and U is an integer type; enum-cast
Problem #1: potentially_mutate
is incorrectly marked as a safe function while it can cause undefined behavior (e.g. what if I pass in an invalid pointer?). So we need to mark it unsafe
and document our assumptions for safe usage:
/// Safety: must pass a valid mutable pointer.
unsafe fn potentially_mutate(ptr: *mut u8) {
unsafe { *ptr = 0 }
}
So now we need to rewrite why
:
fn why(x: &NonMut) {
let ptr = x.get_bytes().as_ptr() as *mut u8;
unsafe {
potentially_mutate(ptr);
}
}
And now we've exposed our problem #2: we violate the assumptions potentially_mutate
makes. We are passing it in a pointer that points to immutable data, claiming it is mutable. This is undefined behavior! It wouldn't make any difference if we had x: &mut NonMut
either, which is why you'd get a warning if you tried that. What matters is this:
pub fn get_bytes(&self) -> &[u8] {
&self.bytes
}
Here we construct an immutable reference to a slice of bytes when you call the method, regardless of whether your x: &NonMut
is mutable or not. If we wanted to mutate the bytes we would need to also make:
pub fn get_bytes_mut(&mut self) -> &mut [u8] {
&mut self.bytes
}
You would still not be done however, as as_ptr
states:
The caller must also ensure that the memory the pointer (non-transitively) points to is never written to (except inside an UnsafeCell) using this pointer or any pointer derived from it. If you need to mutate the contents of the slice, use as_mut_ptr.
And as_mut_ptr
states:
The caller must ensure that the slice outlives the pointer this function returns, or else it will end up pointing to garbage.
So to get your example correct and safe:
struct Mut {
bytes: [u8; 5]
}
impl Mut {
pub fn get_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn get_bytes_mut(&mut self) -> &mut [u8] {
&mut self.bytes
}
}
unsafe fn potentially_mutate(ptr: *mut u8) {
*ptr = 0
}
fn whynot(x: &mut Mut) {
unsafe {
let slice = x.get_bytes_mut(); // Keep slice alive.
let ptr = slice.as_mut_ptr();
potentially_mutate(ptr);
}
}
fn main() {
let mut x = Mut { bytes: [1, 2, 3, 4, 5] };
println!("{}", x.get_bytes()[0]);
whynot(&mut x);
println!("{}", x.get_bytes()[0]);
}
You are not casting from &u8
to *mut u8
directly.
fn allowed_but_ub(x: &[u8]) {
let x_ptr = x.as_ptr(); // &[u8] -> *const u8
let _ptr = x_ptr as *mut u8; // *const u8 -> *mut u8
}
Playground
Casting from *const T
to *mut T
is allowed.
This might be useful if you want to abstract over the mutability and have some runtime check that tells you whether your pointer came from a mutable or immutable source.
Directly casting an immutable reference to a mutable pointer is not allowed, though, and as such the following example fails to compile as expected:
fn not_allowed(x: &[u8]) {
let x_ref = &x[0]; // &[u8] -> &u8
let _ptr = x_ref as *mut u8; // &u8 -> *mut u8
}
Playground
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With