Skip to main content

Memory Layout and repr in Rust: FFI Safety, Alignment, and Performance

Created: April 24, 2026 CalmOps 3 min read

Why Layout Matters

Memory layout in Rust is not just an optimization detail. It directly affects:

  1. FFI correctness.
  2. serialization compatibility.
  3. cache efficiency.
  4. unsafe code soundness.

If you cross language boundaries or write low-level code, repr choices are critical.

repr(Rust): Default Representation

Without any attribute, Rust is free to choose layout optimizations.

Implications:

  1. field order is not part of stable ABI contract.
  2. layout may differ across compiler versions/targets.
  3. never assume C-compatible memory layout.

Use default representation for pure Rust internal types.

Padding and Alignment Basics

CPU alignment constraints often add padding bytes.

struct Example {
    a: u8,
    b: u64,
    c: u32,
}

The logical field sizes are 1 + 8 + 4, but actual size is larger because b needs alignment.

Inspect layout safely:

use std::mem::{align_of, size_of};

println!("size={} align={}", size_of::<Example>(), align_of::<Example>());

repr(C): Stable C ABI Layout

Use #[repr(C)] when interoperating with C.

#[repr(C)]
pub struct CRecord {
    pub id: u32,
    pub flag: u8,
    pub score: f64,
}

repr(C) gives C-compatible field ordering and alignment rules for target platform.

Still validate with both sides’ headers/tests in CI.

repr(transparent) for Newtype Wrappers

repr(transparent) is ideal when wrapping one non-zero-sized field and preserving ABI.

#[repr(transparent)]
pub struct UserId(pub u64);

Useful for type safety without changing calling convention at FFI boundaries.

repr(packed): Use Sparingly

repr(packed) removes/changes padding, but may create unaligned references.

#[repr(packed)]
struct PacketHeader {
    version: u8,
    length: u16,
}

Danger: directly referencing packed fields can be undefined behavior on some architectures.

Safer approach:

  1. copy bytes out using read helpers.
  2. avoid creating aligned references to misaligned fields.

repr(align(N)) for Custom Alignment

Use repr(align(N)) when cache-line or SIMD alignment is needed.

#[repr(align(64))]
struct CacheLine {
    data: [u8; 64],
}

Typical use cases:

  1. reduce false sharing.
  2. optimize high-throughput lock-free structures.

Enum Layout and Niche Optimization

Rust may optimize enum layout using niche values (for example Option<&T> often same size as &T).

This is powerful but should not be treated as stable ABI unless representation is explicitly specified and documented.

FFI Checklist

When exposing Rust types to C/C++:

  1. add repr(C) or repr(transparent).
  2. avoid Rust-specific types in signatures (String, Vec<T>, references).
  3. use raw pointers and explicit lengths.
  4. define ownership rules for allocation/deallocation.
  5. add round-trip integration tests.

Serialization vs In-Memory Layout

Never serialize by blindly dumping struct memory bytes unless layout and endianness are explicitly defined.

Prefer explicit encoders/decoders for stable wire format.

Performance Trade-Offs

  1. Reordering fields can reduce padding.
  2. For hot structs, layout changes can improve cache locality.
  3. Over-optimizing layout without profiling can hurt readability and maintainability.

Measure before and after.

Common Mistakes

  1. Assuming default Rust layout is C-compatible.
  2. Using repr(packed) to save bytes without handling unaligned access safely.
  3. Forgetting target-specific ABI differences.
  4. Exposing unstable internal structs as public FFI contract.

Conclusion

repr attributes are low-level contracts. Use them deliberately:

  1. repr(Rust) for internal flexibility.
  2. repr(C) for FFI.
  3. repr(transparent) for ABI-safe wrappers.
  4. repr(packed) and repr(align) only with clear justification.

The safest approach is explicit contracts + tests across targets.

Resources

Comments

Share this article

Scan to read on mobile