UnsafeByteable

Derive Macro UnsafeByteable 

Source
#[derive(UnsafeByteable)]
Expand description

Derives the Byteable trait for a struct using transmute.

This procedural macro automatically implements the Byteable trait for structs by using std::mem::transmute to convert between the struct and a byte array. This provides zero-overhead serialization but requires careful attention to memory layout and safety.

§Safety

This macro generates unsafe code using std::mem::transmute. You must ensure:

  1. The struct has an explicit memory layout: Use #[repr(C)], #[repr(C, packed)], or #[repr(transparent)] to ensure a well-defined layout.

  2. All byte patterns are valid: Every possible combination of bytes must represent a valid value for your struct. This generally means:

    • Primitive numeric types are fine (u8, i32, f64, etc.)
    • Endianness wrappers are fine (BigEndian<T>, LittleEndian<T>)
    • Arrays of the above are fine
    • Types with invalid bit patterns are NOT safe (bool, char, enums with discriminants, references, NonZero* types, etc.)
  3. No padding with uninitialized memory: When using #[repr(C)] without packed, padding bytes might contain uninitialized memory. Use #[repr(C, packed)] to avoid padding, or ensure all fields are properly aligned.

  4. No complex types: Do not use this with:

    • Types containing pointers or references (&T, Box<T>, Vec<T>, String, etc.)
    • Types with invariants (NonZero*, bool, char, enums with fields, etc.)
    • Types implementing Drop

§Requirements

The struct must:

  • Have a known size at compile time (no dyn traits or unsized fields)
  • Not contain any generic type parameters (or they must implement Byteable)

§Examples

§Basic usage

use byteable::{Byteable, UnsafeByteable};

#[derive(UnsafeByteable, Debug, PartialEq)]
#[repr(C, packed)]
struct Color {
    r: u8,
    g: u8,
    b: u8,
    a: u8,
}

let color = Color { r: 255, g: 128, b: 64, a: 255 };
let bytes = color.as_byte_array();
assert_eq!(bytes, [255, 128, 64, 255]);

let restored = Color::from_byte_array(bytes);
assert_eq!(restored, color);

§With endianness markers

use byteable::{Byteable, BigEndian, LittleEndian, UnsafeByteable};

#[derive(UnsafeByteable, Debug)]
#[repr(C, packed)]
struct NetworkPacket {
    magic: BigEndian<u32>,           // Network byte order
    version: u8,
    flags: u8,
    payload_len: LittleEndian<u16>,  // Different endianness for payload
    data: [u8; 16],
}

let packet = NetworkPacket {
    magic: 0x12345678.into(),
    version: 1,
    flags: 0,
    payload_len: 100.into(),
    data: [0; 16],
};

let bytes = packet.as_byte_array();
// magic is big-endian: [0x12, 0x34, 0x56, 0x78]
// payload_len is little-endian: [100, 0]

§With nested structs

use byteable::{Byteable, UnsafeByteable};

#[derive(UnsafeByteable, Debug, Clone, Copy)]
#[repr(C, packed)]
struct Point {
    x: i32,
    y: i32,
}

#[derive(UnsafeByteable, Debug)]
#[repr(C, packed)]
struct Line {
    start: Point,
    end: Point,
}

let line = Line {
    start: Point { x: 0, y: 0 },
    end: Point { x: 10, y: 20 },
};

let bytes = line.as_byte_array();
assert_eq!(bytes.len(), 16); // 4 i32s × 4 bytes each

§With generics (requires bounds)

use byteable::{Byteable, UnsafeByteable};

#[derive(UnsafeByteable, Debug)]
#[repr(C, packed)]
struct Pair<T: Byteable> {
    first: T,
    second: T,
}

let pair = Pair {
    first: 100u32,
    second: 200u32,
};

let bytes = pair.as_byte_array();
assert_eq!(bytes.len(), 8);

§Common Mistakes

§❌ Missing repr attribute

use byteable::UnsafeByteable;

#[derive(UnsafeByteable)]  // ❌ No #[repr(...)] - undefined layout!
struct Bad {
    x: u32,
    y: u16,
}

§❌ Using invalid types

use byteable::UnsafeByteable;

#[derive(UnsafeByteable)]
#[repr(C, packed)]
struct Bad {
    valid: bool,  // ❌ bool has invalid bit patterns (only 0 and 1 are valid)
}

§❌ Using types with pointers

use byteable::UnsafeByteable;

#[derive(UnsafeByteable)]
#[repr(C)]
struct Bad {
    data: Vec<u8>,  // ❌ Contains a pointer - not safe to transmute!
}

§See Also