Skip to main content

transmute_ref

Macro transmute_ref 

Source
macro_rules! transmute_ref {
    ($e:expr) => { ... };
}
Expand description

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

This macro behaves like an invocation of this function:

fn transmute_ref<'src, 'dst, Src, Dst>(src: &'src Src) -> &'dst Dst
where
    'src: 'dst,
    Src: IntoBytes + Immutable + ?Sized,
    Dst: FromBytes + Immutable + ?Sized,
    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_ref! supports transmuting between Sized types, between unsized (i.e., ?Sized) types, and from a Sized type to an unsized type. 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:

let src: &[[u8; 2]] = &[[0, 1], [2, 3]][..];
let dst: &[u8] = transmute_ref!(src);

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

§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:

fn transmute_ref<Src, Dst>(src: &Src) -> &Dst
where
    Src: IntoBytes + Immutable,
    Dst: FromBytes + Immutable,
{
    transmute_ref!(src)
}

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

let src: &u16 = &0;
let dst: &u8 = transmute_ref(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:

let one_dimensional: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];

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

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

Transmuting between unsized types:

#[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 src = Src::ref_from_bytes(&[0, 1, 2, 3, 4, 5, 6, 7]).unwrap();
let dst: &Dst = transmute_ref!(src);

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]);

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

§Use in const contexts

This macro can be invoked in const contexts only when Src: Sized and Dst: Sized.

§Code Generation

The below code generation benchmark exercises this routine on a destination type whose complex layout places complex requirements on the source:

  • the source must begin an even memory address
  • the source has a minimum length of 4 bytes
  • the source has a total length divisible by 2

These conditions are all checked at compile time.

Format
use zerocopy_derive::*;

// The only valid value of this type are the bytes `0xC0C0`.
#[derive(TryFromBytes, KnownLayout, Immutable)]
#[repr(u16)]
pub enum C0C0 {
    _XC0C0 = 0xC0C0,
}

#[derive(FromBytes, KnownLayout, Immutable)]
#[repr(C, align(2))]
pub struct Packet<Magic> {
    magic_number: Magic,
    mug_size: u8,
    temperature: u8,
    marshmallows: [[u8; 2]],
}

/// A packet begining with the magic number `0xC0C0`.
pub type CocoPacket = Packet<C0C0>;

/// A packet beginning with any two initialized bytes.
pub type LocoPacket = Packet<[u8; 2]>;
Benchmark
use zerocopy_derive::*;

#[path = "formats/coco.rs"]
mod format;

#[derive(IntoBytes, KnownLayout, Immutable)]
#[repr(C, align(2))]
struct MinimalViableSource {
    header: [u8; 4],
    trailer: [[u8; 2]],
}

#[unsafe(no_mangle)]
fn codegen_test(source: &MinimalViableSource) -> &format::LocoPacket {
    zerocopy::transmute_ref!(source)
}
Machine Code Analysis

§Replication

You may replicate this analysis on your device with cargo-show-asm by running:

cargo asm --bench transmute_ref codegen_test --mca

§Results

Iterations:        100
Instructions:      300
Total Cycles:      104
Total uOps:        300

Dispatch Width:    4
uOps Per Cycle:    2.88
IPC:               2.88
Block RThroughput: 1.0


Instruction Info:
[1]: #uOps
[2]: Latency
[3]: RThroughput
[4]: MayLoad
[5]: MayStore
[6]: HasSideEffects (U)

[1]    [2]    [3]    [4]    [5]    [6]    Instructions:
 1      1     0.33                        mov	rdx, rsi
 1      1     0.33                        mov	rax, rdi
 1      1     1.00                  U     ret


Resources:
[0]   - SBDivider
[1]   - SBFPDivider
[2]   - SBPort0
[3]   - SBPort1
[4]   - SBPort4
[5]   - SBPort5
[6.0] - SBPort23
[6.1] - SBPort23


Resource pressure per iteration:
[0]    [1]    [2]    [3]    [4]    [5]    [6.0]  [6.1]  
 -      -     0.99   1.00    -     1.01    -      -     

Resource pressure by instruction:
[0]    [1]    [2]    [3]    [4]    [5]    [6.0]  [6.1]  Instructions:
 -      -     0.99    -      -     0.01    -      -     mov	rdx, rsi
 -      -      -     1.00    -      -      -      -     mov	rax, rdi
 -      -      -      -      -     1.00    -      -     ret