Is This The Right Way to Transmut &mut [u8] to &mut MyType?

This works, but I wanted to make sure it was sound and also make sure there wasn't a safer way than transmut.

#![allow(warnings)]

#[repr(C)]
#[derive(Debug)]
struct MyType {
    num1: u8,
    num2: u8,
}

fn main() {
    let mut storage = [1u8, 2, 3, 4];
    let my_type_slice_2 = &mut storage[2..=3];
    
    let my_type_2 = unsafe {
        std::mem::transmute::<*mut u8, &mut MyType>(my_type_slice_2.as_mut_ptr())
    };
    dbg!(my_type_2);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.49s
     Running `target/debug/playground`
[src/main.rs:17] my_type_2 = MyType {
    num1: 3,
    num2: 4,
}

Do not use transmute when you could use pointer casts. Additionally, you should prefer to perform such transmutes in a separate function to properly tie the lifetimes together. In your program, the compiler does not check that my_type_2 must not be used after storage is destroyed, but it will in the example below.

#[repr(C)]
#[derive(Debug)]
struct MyType {
    num1: u8,
    num2: u8,
}

impl MyType {
    fn from_slice(s: &mut [u8]) -> &mut Self {
        assert!(s.len() == 2);
        unsafe {
            &mut *(s.as_mut_ptr() as *mut Self)
        }
    }
}

fn main() {
    let mut storage = [1u8, 2, 3, 4];
    let my_type_slice_2 = &mut storage[2..=3];
    
    let my_type_2 = MyType::from_slice(my_type_slice_2);
    dbg!(my_type_2);
}
3 Likes

My understanding from the Rustonomicon was that transmute is marginally safer than pointer casts because it at least has a sanity check that sizes are equal:

Also of course you can get all of the functionality of [transmute] using raw pointer casts or union s, but without any of the lints or other basic sanity checks. Raw pointer casts and union s do not magically avoid the above rules.

Has this advice changed since that was written?

That refers to a different situation. All (non-fat) pointers have the same size, so the check does not have any effect when converting between them. When converting between pointers, you should generally use pointer casts.

The bytemuck crate provides an unsafe but sound API for this.


extern crate bytemuck;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct MyType {
    num1: u8,
    num2: u8,
}

// unsafe promises about the ABI properties of MyType
unsafe impl bytemuck::Pod for MyType {}
unsafe impl bytemuck::Zeroable for MyType {}

fn main() {
    let mut storage = [1u8, 2, 3, 4];
    let my_type_slice_2 = &mut storage[2..=3];
    
    // because bytemuck unsafe traits are implemented, 
    // this is a safe function call
    let my_type_2: &mut [MyType] = bytemuck::cast_slice_mut(my_type_slice_2);
    dbg!(my_type_2);
}
3 Likes

In this case your type has the same alignment as the input, but in general case, you have to pay attention to the alignment, because it is not safe to cast [u8] to [T] if T has fields larger than a byte.

2 Likes

OK, I'll try out bytemuck. Thanks for the answers, all. :slight_smile: