try_transmute_ref

Macro try_transmute_ref 

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

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

Note that while the value of the referent is checked for validity at runtime, the size and alignment are checked at compile time. For conversions which are fallible with respect to size and alignment, see the methods on TryFromBytes.

This macro behaves like an invocation of this function:

fn try_transmute_ref<Src, Dst>(src: &Src) -> Result<&Dst, ValidityError<&Src, Dst>>
where
    Src: IntoBytes + Immutable + ?Sized,
    Dst: TryFromBytes + 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

try_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] = try_transmute_ref!(src).unwrap();

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

§Examples

Transmuting between Sized types:

// 0u8 → bool = false
assert_eq!(try_transmute_ref!(&0u8), Ok(&false));

// 1u8 → bool = true
 assert_eq!(try_transmute_ref!(&1u8), Ok(&true));

// 2u8 → bool = error
assert!(matches!(
    try_transmute_ref!(&2u8),
    Result::<&bool, _>::Err(ValidityError { .. })
));

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, bool>;

let src = Src::ref_from_bytes(&[0, 1, 0, 1, 0, 1, 0, 1]).unwrap();
let dst: &Dst = try_transmute_ref!(src).unwrap();

assert_eq!(src.t.as_bytes(), [0, 1, 0, 1]);
assert_eq!(src.u.len(), 2);
assert_eq!(src.u.as_bytes(), [0, 1, 0, 1]);

assert_eq!(dst.t.as_bytes(), [0, 1]);
assert_eq!(dst.u, [false, true, false, true, false, true]);

§ 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_try_transmute_ref_static_size(
   source: &MinimalViableSource,
) -> Option<&format::CocoPacket> {
   zerocopy::try_transmute_ref!(source).ok()
}
Assembly
bench_try_transmute_ref_static_size:
   xor eax, eax
   cmp word ptr [rdi], -16192
   cmove rax, rdi
   ret
Machine Code Analysis
Iterations:        100
Instructions:      400
Total Cycles:      160
Total uOps:        600

Dispatch Width:    4
uOps Per Cycle:    3.75
IPC:               2.50
Block RThroughput: 1.5


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

[1]    [2]    [3]    [4]    [5]    [6]    Instructions:
1      0     0.25                        xor	eax, eax
2      6     0.50    *                   cmp	word ptr [rdi], -16192
2      2     0.67                        cmove	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]  
-      -     1.02   1.48    -     1.50   0.50   0.50   

Resource pressure by instruction:
[0]    [1]    [2]    [3]    [4]    [5]    [6.0]  [6.1]  Instructions:
-      -      -      -      -      -      -      -     xor	eax, eax
-      -     0.02   0.49    -     0.49   0.50   0.50   cmp	word ptr [rdi], -16192
-      -     1.00   0.99    -     0.01    -      -     cmove	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_try_transmute_ref_dynamic_size(
   source: &MinimalViableSource,
) -> Option<&format::CocoPacket> {
   zerocopy::try_transmute_ref!(source).ok()
}
Assembly
bench_try_transmute_ref_dynamic_size:
   lea rdx, [rsi + 1]
   xor eax, eax
   cmp word ptr [rdi], -16192
   cmove rax, rdi
   ret
Machine Code Analysis
Iterations:        100
Instructions:      500
Total Cycles:      209
Total uOps:        700

Dispatch Width:    4
uOps Per Cycle:    3.35
IPC:               2.39
Block RThroughput: 1.8


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.50                        lea	rdx, [rsi + 1]
1      0     0.25                        xor	eax, eax
2      6     0.50    *                   cmp	word ptr [rdi], -16192
2      2     0.67                        cmove	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]  
-      -     1.50   1.51    -     1.99   0.50   0.50   

Resource pressure by instruction:
[0]    [1]    [2]    [3]    [4]    [5]    [6.0]  [6.1]  Instructions:
-      -     0.51   0.49    -      -      -      -     lea	rdx, [rsi + 1]
-      -      -      -      -      -      -      -     xor	eax, eax
-      -      -     0.02    -     0.98   0.50   0.50   cmp	word ptr [rdi], -16192
-      -     0.99   1.00    -     0.01    -      -     cmove	rax, rdi
-      -      -      -      -     1.00    -      -     ret