Macro transmute_mut

macro_rules! transmute_mut {
    ($e:expr) => { ... };
}

Safely transmutes a mutable reference of one type to a mutable reference of another type of the same size and compatible alignment.

This macro behaves like an invocation of this function:

const fn transmute_mut<'src, 'dst, Src, Dst>(src: &'src mut Src) -> &'dst mut Dst
where
    'src: 'dst,
    Src: FromBytes + IntoBytes,
    Dst: FromBytes + IntoBytes,
    align_of::<Src>() >= align_of::<Dst>(),
    size_compatible::<Src, Dst>(),
{
# /*
    ...
# */
}

The types Src and Dst are inferred from the calling context; they cannot be explicitly specified in the macro invocation.

Size compatibility

transmute_mut! supports transmuting between Sized types or between unsized (i.e., ?Sized) types. It supports any transmutation that preserves the number of bytes of the referent, even if doing so requires updating the metadata stored in an unsized "fat" reference:

# use zerocopy::transmute_mut;
# use core::mem::size_of_val; // Not in the prelude on our MSRV
let src: &mut [[u8; 2]] = &mut [[0, 1], [2, 3]][..];
let dst: &mut [u8] = transmute_mut!(src);

assert_eq!(dst.len(), 4);
assert_eq!(dst, [0, 1, 2, 3]);
let dst_size = size_of_val(dst);
assert_eq!(src.len(), 2);
assert_eq!(size_of_val(src), dst_size);

Errors

Violations of the alignment and size compatibility checks are detected after the compiler performs monomorphization. This has two important consequences.

First, it means that generic code will never fail these conditions:

# use zerocopy::{transmute_mut, FromBytes, IntoBytes, Immutable};
fn transmute_mut<Src, Dst>(src: &mut Src) -> &mut Dst
where
    Src: FromBytes + IntoBytes,
    Dst: FromBytes + IntoBytes,
{
    transmute_mut!(src)
}

Instead, failures will only be detected once generic code is instantiated with concrete types:

# use zerocopy::{transmute_mut, FromBytes, IntoBytes, Immutable};
#
# fn transmute_mut<Src, Dst>(src: &mut Src) -> &mut Dst
# where
#     Src: FromBytes + IntoBytes,
#     Dst: FromBytes + IntoBytes,
# {
#     transmute_mut!(src)
# }
let src: &mut u16 = &mut 0;
let dst: &mut u8 = transmute_mut(src);

Second, the fact that violations are detected after monomorphization means that cargo check will usually not detect errors, even when types are concrete. Instead, cargo build must be used to detect such errors.

Examples

Transmuting between Sized types:

# use zerocopy::transmute_mut;
let mut one_dimensional: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];

let two_dimensional: &mut [[u8; 4]; 2] = transmute_mut!(&mut one_dimensional);

assert_eq!(two_dimensional, &[[0, 1, 2, 3], [4, 5, 6, 7]]);

two_dimensional.reverse();

assert_eq!(one_dimensional, [4, 5, 6, 7, 0, 1, 2, 3]);

Transmuting between unsized types:

# use {zerocopy::*, zerocopy_derive::*};
# type u16 = zerocopy::byteorder::native_endian::U16;
# type u32 = zerocopy::byteorder::native_endian::U32;
#[derive(KnownLayout, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
struct SliceDst<T, U> {
    t: T,
    u: [U],
}

type Src = SliceDst<u32, u16>;
type Dst = SliceDst<u16, u8>;

let mut bytes = [0, 1, 2, 3, 4, 5, 6, 7];
let src = Src::mut_from_bytes(&mut bytes[..]).unwrap();
let dst: &mut Dst = transmute_mut!(src);

assert_eq!(dst.t.as_bytes(), [0, 1]);
assert_eq!(dst.u, [2, 3, 4, 5, 6, 7]);

assert_eq!(src.t.as_bytes(), [0, 1, 2, 3]);
assert_eq!(src.u.len(), 2);
assert_eq!(src.u.as_bytes(), [4, 5, 6, 7]);