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

This abstraction is safe and cheap, but does not necessarily have zero runtime cost. The codegen you experience in practice will depend on optimization level, the layout of the destination type, and what the compiler can prove about the source.

The below examples illustrate typical codegen for increasingly complex types:

Sized
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_static_size.rs"]
mod format;

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

#[unsafe(no_mangle)]
fn bench_transmute_ref_static_size(source: &MinimalViableSource) -> &format::LocoPacket {
   zerocopy::transmute_ref!(source)
}
Assembly
bench_transmute_ref_static_size:
   mov rax, rdi
   ret
Machine Code Analysis
Iterations:        100
Instructions:      200
Total Cycles:      104
Total uOps:        200

Dispatch Width:    4
uOps Per Cycle:    1.92
IPC:               1.92
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	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.49   0.50    -     1.01    -      -     

Resource pressure by instruction:
[0]    [1]    [2]    [3]    [4]    [5]    [6.0]  [6.1]  Instructions:
-      -     0.49   0.50    -     0.01    -      -     mov	rax, rdi
-      -      -      -      -     1.00    -      -     ret
Unsized
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_dynamic_size.rs"]
mod format;

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

#[unsafe(no_mangle)]
fn bench_transmute_ref_dynamic_size(source: &MinimalViableSource) -> &format::LocoPacket {
   zerocopy::transmute_ref!(source)
}
Assembly
bench_transmute_ref_dynamic_size:
   mov rax, rdi
   lea rdx, [rsi + 1]
   ret
Machine Code Analysis
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	rax, rdi
1      1     0.50                        lea	rdx, [rsi + 1]
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	rax, rdi
-      -      -     1.00    -      -      -      -     lea	rdx, [rsi + 1]
-      -      -      -      -     1.00    -      -     ret