Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting to `*mut` overrules the reference not being `mut`

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?

like image 278
V0ldek Avatar asked Nov 10 '21 13:11

V0ldek


People also ask

What is the argument of &Mut U8 used for?

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.

What is the difference between *something and &mut T?

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.

When is a cast E as U valid?

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


Video Answer


2 Answers

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]);
}
like image 111
orlp Avatar answered Oct 20 '22 20:10

orlp


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

like image 35
Skgland Avatar answered Oct 14 '22 03:10

Skgland