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.
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.
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);
}
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.