// 722. repr(C), repr(packed), repr(align(N)) layouts
//
// Shows how to control struct layout and inspect it at compile time.
use std::mem;
// ── repr(Rust) — default, unspecified order ───────────────────────────────────
/// Default layout: compiler may reorder fields to minimise padding.
/// Do NOT use for FFI or when byte layout matters.
struct DefaultLayout {
a: u8, // 1 byte
b: u32, // 4 bytes — compiler will likely place this first to avoid padding
c: u16, // 2 bytes
}
// Typical: b(4) + c(2) + a(1) + pad(1) = 8 bytes
// ── repr(C) — C-compatible, field order preserved ─────────────────────────────
/// Fields are laid out in declaration order with C padding rules.
/// Safe to pass across FFI boundaries.
#[repr(C)]
pub struct CLayout {
pub a: u8, // offset 0, size 1
// 3 bytes padding (to align b to 4)
pub b: u32, // offset 4, size 4
pub c: u16, // offset 8, size 2
// 2 bytes padding (to align struct to max(4) = 4)
}
// Total: 12 bytes
/// A C-compatible packet header — safe to `memcpy` to/from a C struct.
#[repr(C)]
pub struct PacketHeader {
pub magic: u32,
pub version: u8,
pub flags: u8,
pub length: u16,
pub checksum: u32,
}
// ── repr(packed) — no padding ─────────────────────────────────────────────────
/// All padding removed. Fields may be unaligned.
///
/// # Warning
/// Never take a reference (`&`) to a field — that would be UB on strict-align
/// architectures. Use `ptr::read_unaligned` / `ptr::write_unaligned` instead.
#[repr(C, packed)]
pub struct PackedLayout {
pub a: u8,
pub b: u32,
pub c: u16,
}
// Total: 1 + 4 + 2 = 7 bytes — no padding
/// Read a packed field safely (no reference taken to unaligned field).
impl PackedLayout {
pub fn b_value(&self) -> u32 {
// SAFETY: `self` is a valid `PackedLayout` instance. We use
// `read_unaligned` because `b` may not be 4-byte aligned due to packed.
unsafe { std::ptr::addr_of!(self.b).read_unaligned() }
}
pub fn c_value(&self) -> u16 {
// SAFETY: same reasoning as `b_value`.
unsafe { std::ptr::addr_of!(self.c).read_unaligned() }
}
}
// ── repr(align(N)) — forced minimum alignment ─────────────────────────────────
/// Align to 64 bytes (one full cache line).
/// Prevents false sharing between CPU cores in concurrent data structures.
#[repr(C, align(64))]
pub struct CacheLinePadded<T> {
pub value: T,
// Implicit padding to reach 64-byte size if needed.
}
/// Two u64s, each on their own cache line.
pub struct NoPseudoSharing {
pub a: CacheLinePadded<u64>,
pub b: CacheLinePadded<u64>,
}
/// Align to 32 bytes for AVX2 SIMD loads (requires 32-byte alignment).
#[repr(C, align(32))]
pub struct Avx2Aligned {
pub data: [f32; 8], // 256-bit = 8 × f32
}
// ── repr(transparent) — single-field newtype ──────────────────────────────────
/// Same layout as the inner type — zero-cost newtype pattern.
#[repr(transparent)]
pub struct Meters(pub f64);
#[repr(transparent)]
pub struct Seconds(pub f64);
// ── Compile-time layout assertions ───────────────────────────────────────────
const _: () = {
assert!(mem::size_of::<PackedLayout>() == 7);
assert!(mem::size_of::<PacketHeader>() == 12);
assert!(mem::align_of::<CacheLinePadded<u64>>() == 64);
assert!(mem::align_of::<Avx2Aligned>() == 32);
assert!(mem::size_of::<Meters>() == mem::size_of::<f64>());
};
// ── main ──────────────────────────────────────────────────────────────────────
fn print_layout(name: &str, size: usize, align: usize) {
println!(" {name:<30} size={size:3} align={align:3}");
}
fn main() {
println!("=== Layout report ===");
print_layout("DefaultLayout", mem::size_of::<DefaultLayout>(), mem::align_of::<DefaultLayout>());
print_layout("CLayout (repr(C))", mem::size_of::<CLayout>(), mem::align_of::<CLayout>());
print_layout("PackedLayout", mem::size_of::<PackedLayout>(), mem::align_of::<PackedLayout>());
print_layout("PacketHeader", mem::size_of::<PacketHeader>(), mem::align_of::<PacketHeader>());
print_layout("CacheLinePadded<u64>", mem::size_of::<CacheLinePadded<u64>>(), mem::align_of::<CacheLinePadded<u64>>());
print_layout("Avx2Aligned", mem::size_of::<Avx2Aligned>(), mem::align_of::<Avx2Aligned>());
print_layout("Meters (transparent)", mem::size_of::<Meters>(), mem::align_of::<Meters>());
println!("\n=== Field offsets (CLayout, repr(C)) ===");
println!(" a @ offset {}", std::mem::offset_of!(CLayout, a));
println!(" b @ offset {}", std::mem::offset_of!(CLayout, b));
println!(" c @ offset {}", std::mem::offset_of!(CLayout, c));
// Packed: read via unaligned helpers
let packed = PackedLayout { a: 0xAA, b: 0x1234_5678, c: 0xBEEF };
println!("\n=== PackedLayout unaligned reads ===");
println!(" a=0x{:02X} b=0x{:08X} c=0x{:04X}", packed.a, packed.b_value(), packed.c_value());
// Cache-line padding
let cs = NoPseudoSharing {
a: CacheLinePadded { value: 1 },
b: CacheLinePadded { value: 2 },
};
println!("\n=== No false sharing ===");
let a_addr = &cs.a as *const _ as usize;
let b_addr = &cs.b as *const _ as usize;
println!(" &a=0x{a_addr:016X} &b=0x{b_addr:016X} diff={} bytes", b_addr - a_addr);
assert!(b_addr - a_addr >= 64, "must be on different cache lines");
}
// ── Tests ─────────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn packed_size_is_seven() {
assert_eq!(mem::size_of::<PackedLayout>(), 7);
}
#[test]
fn cache_line_align() {
assert_eq!(mem::align_of::<CacheLinePadded<u64>>(), 64);
assert!(mem::size_of::<CacheLinePadded<u64>>() >= 64);
}
#[test]
fn packet_header_size() {
assert_eq!(mem::size_of::<PacketHeader>(), 12);
}
#[test]
fn transparent_same_size() {
assert_eq!(mem::size_of::<Meters>(), mem::size_of::<f64>());
assert_eq!(mem::align_of::<Meters>(), mem::align_of::<f64>());
}
#[test]
fn packed_unaligned_read() {
let p = PackedLayout { a: 1, b: 0xDEAD_BEEF, c: 0x1234 };
assert_eq!(p.b_value(), 0xDEAD_BEEF);
assert_eq!(p.c_value(), 0x1234);
}
#[test]
fn c_layout_offsets() {
assert_eq!(std::mem::offset_of!(CLayout, a), 0);
assert_eq!(std::mem::offset_of!(CLayout, b), 4);
assert_eq!(std::mem::offset_of!(CLayout, c), 8);
}
}
(* OCaml: Memory layout inspection
OCaml doesn't expose repr attributes, but we can observe sizes
using Obj and compare with C layouts via ctypes (not used here — std only). *)
(* OCaml's Obj module lets us inspect the runtime layout *)
(* --- Field ordering and padding in OCaml --- *)
(* OCaml records are laid out in declaration order; each field is one word.
There is no padding between fields (they're always word-sized). *)
type compact = { a: int; b: bool; c: int }
(* In OCaml: a=1 word, b=1 word (bool is boxed as int), c=1 word = 3 words *)
type c_like = { x: float; y: float; z: float }
(* float array is unboxed; float record fields are also unboxed *)
let () =
(* Obj.size gives number of fields (words, not bytes) *)
let v = { a = 1; b = true; c = 3 } in
Printf.printf "compact: Obj.size=%d words\n" (Obj.size (Obj.repr v));
let p = { x = 1.0; y = 2.0; z = 3.0 } in
Printf.printf "c_like: Obj.size=%d words\n" (Obj.size (Obj.repr p));
(* On 64-bit: 1 word = 8 bytes *)
let word_size = Sys.int_size / 8 + 1 in
Printf.printf "Word size: %d bytes\n" word_size
(* --- Simulated packed / aligned structs --- *)
(* OCaml has no packed or align attributes.
For FFI, you'd use the `ctypes` library which has Ctypes.Struct. *)
(* ctypes conceptual example (not compiled — just illustration):
open Ctypes
let my_struct = structure "my_struct"
let f1 = field my_struct "field1" uint8_t
let f2 = field my_struct "field2" uint32_t
let () = seal my_struct
(* ctypes computes the right C layout with padding *)
*)
(* --- Byte-level layout via Bytes --- *)
(* The closest OCaml can get to packed repr is Bytes/Bigarray *)
let pack_u8_u32 (a : int) (b : int32) : bytes =
let buf = Bytes.create 5 in
Bytes.set buf 0 (Char.chr (a land 0xFF));
(* Little-endian u32 *)
Bytes.set buf 1 (Char.chr (Int32.to_int (Int32.logand b 0xFFl)));
Bytes.set buf 2 (Char.chr (Int32.to_int (Int32.logand (Int32.shift_right_logical b 8) 0xFFl)));
Bytes.set buf 3 (Char.chr (Int32.to_int (Int32.logand (Int32.shift_right_logical b 16) 0xFFl)));
Bytes.set buf 4 (Char.chr (Int32.to_int (Int32.logand (Int32.shift_right_logical b 24) 0xFFl)));
buf
let () =
let packed = pack_u8_u32 0xAB 0x12345678l in
Printf.printf "Packed bytes: %s\n"
(String.concat " " (List.init (Bytes.length packed)
(fun i -> Printf.sprintf "%02X" (Char.code (Bytes.get packed i)))))