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