| |

Rust 98 🦀 Memory Layouts: #[repr(C)], Alignment, and Structure Packing

⏱️ Estimated read: 18 min | |

Rust’s default memory layout is deliberately unspecified. The compiler is free to reorder fields, insert padding, and choose representations that minimize size or maximize access speed, and it may change those decisions between compilation sessions or target platforms. For most code this is invisible and beneficial. But when a type crosses an FFI boundary, when its byte representation must match a documented protocol, or when cache-line behavior matters for performance, the programmer needs to take control. The #[repr(...)] attributes are that control.

This chapter covers the three representations that matter most for low-level work: #[repr(C)], which imposes C’s layout rules so that types are ABI-compatible; #[repr(align(N))], which raises a type’s alignment to a specified boundary; and #[repr(packed)], which removes padding at the cost of potentially unaligned fields. The chapter explains the size and alignment algorithm that repr(C) follows, the safety hazards of packed structs, and the situations where layout control is worth the cost. It closes with the discipline required to keep layout assumptions from becoming silent correctness bugs.


Key point: #[repr(C)] does not make a type “safe” or “portable.” It makes its layout predictable and C-compatible on the current target. Predictability is a tool, not a guarantee that the layout matches any particular external definition.


Why layout control exists

The FFI boundary. Any type passed across a call to C or C++ must have a layout that the foreign compiler agrees on. Rust’s default layout offers no such agreement: the compiler may reorder fields or add padding in ways that C would not. #[repr(C)] changes the type’s layout to follow the target platform’s C ABI, which is the only representation that foreign code can be expected to understand . Without it, passing a struct to a C function is undefined behavior even if the fields look identical.

The byte-level protocol. Some data formats are defined in terms of exact byte offsets: network headers, file formats, memory-mapped hardware registers. Rust’s default layout makes no promises about where a field lives, so reading a file header into a struct without repr(C) is not sound. With repr(C), the field order and offsets are determined by a documented algorithm, and the programmer can compute the size and offsets in advance .

The alignment requirement. Some hardware and some algorithms require data to be aligned to a specific boundary. A SIMD register load may fault if its address is not 16-byte aligned. A lock-free algorithm may need a value to occupy a full cache line so that adjacent data does not cause false sharing. #[repr(align(N))] raises a type’s alignment to a power of two, which makes these layouts expressible .

The size constraint. In embedded systems or in performance-critical data structures, every byte of padding is a cost. #[repr(packed)] removes all padding between fields, producing a type whose size is the sum of its fields’ sizes. This is useful for wire formats and for arrays of small structs where the padding would waste memory. The cost is that fields may be unaligned, and taking a reference to an unaligned field is undefined behavior .

The optimization trade-off. Rust’s default layout is chosen to be efficient for the compiler’s code generation. It may reorder fields to reduce padding, which usually makes the type smaller and improves cache utilization. Overriding that layout with repr(C) preserves declaration order, which may introduce padding that the compiler would otherwise have eliminated. Layout control is therefore a deliberate trade: predictability and compatibility at the cost of the compiler’s size optimizations .

The stability guarantee. repr(C) layout is stable across compiler versions for a given target, because it is defined in terms of the C ABI. The default repr(Rust) layout is explicitly unstable: the compiler may change field ordering or padding between releases. Any code that depends on the memory representation of a type must use repr(C) (or another explicit representation) to avoid breaking on a compiler upgrade .


a. The repr(C) layout algorithm

#[repr(C)] guarantees that a struct’s fields are laid out in declaration order, with each field at an offset that satisfies its alignment requirement. The algorithm for computing size and offsets is specified and stable: process fields in order, add each field’s size, and round up the current offset to the next field’s alignment before placing it. After the last field, round the total size up to the struct’s alignment, which is the maximum alignment of any field (or the alignment set by repr(align)) .

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

#[repr(C)]
struct Header {
    tag: u8,      // offset 0, size 1
    len: u32,     // alignment 4 → offset 4, size 4
    flags: u8,    // offset 8, size 1
}

// Layout:
//   tag:    bytes 0..1
//   pad:    bytes 1..4   (3 bytes of padding)
//   len:    bytes 4..8
//   flags:  bytes 8..9
//   pad:    bytes 9..12  (3 bytes of tail padding)
// Total size: 12, alignment: 4

fn main() {
    assert_eq!(offset_of!(Header, tag), 0);
    assert_eq!(offset_of!(Header, len), 4);
    assert_eq!(offset_of!(Header, flags), 8);
    assert_eq!(size_of::<Header>(), 12);
    assert_eq!(align_of::<Header>(), 4);
}

The padding is not an implementation detail; it is part of the layout. A C compiler on the same target will produce the same offsets and the same total size, provided the field types have the same sizes and alignments. This is what makes repr(C) suitable for FFI: the two compilers agree on the byte layout because they follow the same rules.

Reordering the fields can eliminate padding, but repr(C) does not allow reordering. The programmer must do it manually if size matters. The following struct moves the u8 fields together so that the u32 follows them without intervening padding.

#[repr(C)]
struct PackedHeader {
    tag: u8,      // offset 0
    flags: u8,    // offset 1
    len: u32,     // alignment 4 → offset 4
}

// No interior padding: tag and flags occupy bytes 0..2,
// bytes 2..4 are padding to align len, len occupies 4..8.
// Total size: 8, alignment: 4.

The size dropped from 12 to 8, a 33% reduction, purely by reordering fields. This is the optimization that repr(Rust) performs automatically and that repr(C) forbids. The trade-off is explicit: the programmer gains layout predictability and loses the compiler’s willingness to reorganize.

For enums, repr(C) has a different meaning. A fieldless enum with repr(C) uses the target platform’s default C enum size and signedness, which is implementation-defined in C itself . An enum with fields and repr(C) receives a defined bridging representation, but it is not directly a C enum. The repr(u8), repr(u16), and related attributes specify the discriminant size explicitly and are often preferable when the exact size matters.


b. Alignment control with repr(align(N))

#[repr(align(N))] raises a type’s alignment to at least N, which must be a power of two. The size of the type is then rounded up to a multiple of N, because an array of the type must have every element aligned . This is the tool for cache-line isolation, SIMD alignment, and any other case where the hardware requires or rewards a specific address boundary.

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

#[repr(align(64))]
struct CacheLine {
    value: u64,   // 8 bytes of data
}

// The struct is aligned to 64 bytes, the typical cache line size.
// Its size is also 64 bytes: 8 bytes of data, 56 bytes of padding.
// An array of CacheLine values places each element on its own cache line.

fn main() {
    assert_eq!(align_of::<CacheLine>(), 64);
    assert_eq!(size_of::<CacheLine>(), 64);
}

The cache-line use case is the canonical example. When two threads write to different fields that happen to share a cache line, the hardware’s cache coherence protocol forces both cores to contend for the same line, a phenomenon called false sharing. Padding each value to a full cache line eliminates the contention. The cost is memory: 56 bytes of padding per 8 bytes of data in the example above. The benefit is that concurrent writes proceed at full speed instead of serializing through the coherence protocol.

repr(align(N)) can be combined with repr(C) to control both field order and overall alignment. The two attributes do not conflict; repr(C) determines the relative offsets, and repr(align) raises the base alignment and the final size.

#[repr(C, align(16))]
struct AlignedPacket {
    kind: u8,     // offset 0
    length: u32,  // offset 4
}

// size_of: 8 bytes of data, rounded up to 16
// align_of: 16
// Every AlignedPacket starts at a 16-byte boundary.

repr(align(N)) is incompatible with repr(packed). The two attributes express opposite intentions: one raises alignment, the other removes the padding that alignment requires. The compiler rejects the combination rather than choosing a resolution .


c. Packing and the unaligned reference hazard

#[repr(packed)] removes all padding between fields. The struct’s alignment is reduced to 1 (or to the value in packed(N), if specified), and the size is the sum of the field sizes with no rounding . This produces the smallest possible representation, which is useful for wire formats and for arrays where padding would be wasteful. But it also means that fields may be stored at addresses that violate their natural alignment.

#[repr(C, packed)]
struct WireHeader {
    tag: u8,      // offset 0
    len: u32,     // offset 1, unaligned!
    flags: u8,    // offset 5
}

// Size: 6 bytes (no padding), alignment: 1
// The len field starts at offset 1, which is not a multiple of 4.

The hazard is taking a reference to a packed field. A reference in Rust carries an alignment invariant: &u32 promises that the address is a multiple of 4. If the field is at offset 1, that promise is false, and the reference is undefined behavior. The compiler enforces this with error E0793 when it can detect the violation . The following code does not compile.

// This produces a compile-time error:
// let len_ref = &header.len;  // E0793: reference to packed field is unaligned

The correct way to read a packed field is by value, which lets the compiler generate an unaligned load or a sequence of byte operations. The compiler knows the field is packed and will emit the appropriate code.

// Reading by value is safe and correct.
let len = header.len;
// Writing by value is also correct.
header.len = 42;

When a raw pointer is necessary, std::ptr::addr_of! creates a pointer without forming a reference, and read_unaligned performs the load with an explicit unaligned access.

use std::ptr;

// Create a raw pointer to the packed field without a reference.
let ptr = ptr::addr_of!(header.len);
// Read with an explicitly unaligned operation.
let len = unsafe { ptr.read_unaligned() };

Creating a reference through a raw pointer and then dereferencing it normally is still undefined behavior if the address is unaligned. The unaligned access must be explicit at the point of the load .

A second restriction on packed types is that they cannot contain types that implement Drop. The destructor receives a reference to the field, and that reference would be unaligned. The compiler enforces this restriction at the type level, which means generic types cannot be stored in packed structs without care .


Complete Example Session

// ============================================
// PART 1: THE SETUP
// ============================================
// We build a small packet type and examine how its layout
// changes under different repr attributes.

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

// ============================================
// PART 2: THE DEFAULT LAYOUT
// ============================================
// Rust's default layout may reorder fields. We cannot
// assert offsets for repr(Rust) types.

struct DefaultPacket {
    kind: u8,
    sequence: u32,
    flags: u8,
}

// The compiler may place sequence first, or reorder otherwise.
// No offset assertions are possible.

// ============================================
// PART 3: THE C LAYOUT
// ============================================
// repr(C) fixes the declaration order and the padding.

#[repr(C)]
struct CPacket {
    kind: u8,      // offset 0
    sequence: u32, // alignment 4 → offset 4
    flags: u8,     // offset 8
}

fn c_layout_facts() {
    assert_eq!(offset_of!(CPacket, kind), 0);
    assert_eq!(offset_of!(CPacket, sequence), 4);
    assert_eq!(offset_of!(CPacket, flags), 8);
    assert_eq!(size_of::<CPacket>(), 12);  // 9 bytes data, 3 padding
    assert_eq!(align_of::<CPacket>(), 4);
}

// ============================================
// PART 4: THE REORDERED C LAYOUT
// ============================================
// Moving the u8 fields together removes the interior padding.

#[repr(C)]
struct OptimizedPacket {
    kind: u8,      // offset 0
    flags: u8,     // offset 1
    sequence: u32, // alignment 4 → offset 4
}

fn optimized_layout_facts() {
    assert_eq!(size_of::<OptimizedPacket>(), 8);  // 6 bytes data, 2 padding
}

// ============================================
// PART 5: THE ALIGNED LAYOUT
// ============================================
// repr(align(16)) forces every instance to a 16-byte boundary.

#[repr(C, align(16))]
struct AlignedPacket {
    kind: u8,
    sequence: u32,
    flags: u8,
}

fn aligned_layout_facts() {
    assert_eq!(align_of::<AlignedPacket>(), 16);
    assert_eq!(size_of::<AlignedPacket>(), 16);  // 12 bytes rounded up
}

// ============================================
// PART 6: THE PACKED LAYOUT
// ============================================
// repr(packed) removes all padding. Fields may be unaligned.

#[repr(C, packed)]
struct PackedPacket {
    kind: u8,      // offset 0
    sequence: u32, // offset 1 (unaligned)
    flags: u8,     // offset 5
}

fn packed_layout_facts() {
    assert_eq!(size_of::<PackedPacket>(), 6);
    assert_eq!(align_of::<PackedPacket>(), 1);
    assert_eq!(offset_of!(PackedPacket, sequence), 1);
}

// ============================================
// PART 7: READING PACKED FIELDS SAFELY
// ============================================
// By-value access is safe; reference access is not.

fn read_packed(p: &PackedPacket) -> u32 {
    // Safe: by-value read, compiler emits unaligned load.
    let seq = p.sequence;

    // Also safe: explicit unaligned access through a raw pointer.
    let ptr = ptr::addr_of!(p.sequence);
    let seq2 = unsafe { ptr.read_unaligned() };

    assert_eq!(seq, seq2);
    seq
}

// ============================================
// PART 8: THE FFI CONNECTION
// ============================================
// A repr(C) type can be passed to a C function that
// expects the corresponding C struct.

#[repr(C)]
pub struct CPoint {
    pub x: i32,
    pub y: i32,
}

// extern "C" {
//     fn process_point(p: CPoint) -> i32;
// }
//
// The Rust type and the C struct have identical layouts,
// so the call is sound on a target where i32 is 4 bytes.

// ============================================
// PART 9: THE CACHE-LINE ISOLATION
// ============================================
// Aligning a counter to a cache line prevents false sharing
// when multiple threads increment adjacent counters.

#[repr(align(64))]
struct PaddedCounter {
    value: std::sync::atomic::AtomicU64,
}

fn counter_facts() {
    assert_eq!(align_of::<PaddedCounter>(), 64);
    assert_eq!(size_of::<PaddedCounter>(), 64);
    // An array of PaddedCounter puts each atomic on its own line.
}

// ============================================
// PART 10: THE FULL PICTURE
// ============================================
// Layout attributes are tools for specific problems.
// repr(C) for FFI and byte protocols, repr(align) for
// hardware boundaries, repr(packed) for minimal wire size.
// Each has a cost; none should be applied by default.

fn main() {
    c_layout_facts();
    optimized_layout_facts();
    aligned_layout_facts();
    packed_layout_facts();

    let p = PackedPacket {
        kind: 1,
        sequence: 42,
        flags: 0,
    };
    assert_eq!(read_packed(&p), 42);

    counter_facts();
    println!("all layout assertions passed");
}

The ten parts build a single example that walks through every layout attribute and its consequences. The key observation is that each attribute solves a specific problem and imposes a specific cost: repr(C) costs the compiler’s reordering optimization, repr(align) costs memory, and repr(packed) costs alignment safety. None of them is a default; each is a deliberate choice.


Quick Reference

Representation Attributes

AttributeField OrderPaddingFFI-SafeUse Case
repr(Rust)Compiler-chosenCompiler-chosenNoDefault, performance
repr(C)Declaration orderC rulesYesFFI, byte protocols
repr(align(N))UnchangedRaises alignmentWith repr(C)Cache lines, SIMD
repr(packed)UnchangedRemovedWith repr(C)Wire formats, minimal size

repr(C) Size Algorithm

StepAction
1Start offset at 0
2For each field: round offset up to field’s alignment
3Place field, advance offset by field’s size
4Round final offset up to struct’s alignment
5Struct alignment = max of field alignments (or repr(align))

Alignment Values

TypeAlignment (typical x86-64)
u8, i8, bool1
u16, i162
u32, i32, f324
u64, i64, f64, usize, pointer8
u128, i12816
SIMD (__m256)32

Packed Hazards

OperationSafe?Alternative
p.field (read)Yes—
p.field = x (write)Yes—
&p.fieldNo (E0793)ptr::addr_of!
*ptr from addr_of!Noptr.read_unaligned()
Store Drop type in packedNoNot allowed

Best Practices

✅ Do This:

// Use repr(C) for any type crossing an FFI boundary.
#[repr(C)]                                                          // ✅
struct FFIType { x: i32, y: i32 }

// Reorder fields manually to minimize padding in repr(C).
#[repr(C)]                                                          // ✅
struct Tight { a: u8, b: u8, c: u32 }

// Use repr(align) for cache-line isolation.
#[repr(align(64))]                                                  // ✅
struct CacheLine { value: u64 }

// Read packed fields by value.
let x = packed.field;                                               // ✅

// Use read_unaligned for explicit unaligned access.
unsafe { ptr.read_unaligned() }                                     // ✅

// Combine repr(C) and repr(packed) for FFI wire formats.
#[repr(C, packed)]                                                  // ✅
struct Wire { a: u8, b: u32 }

❌ Don’t Do This:

// Don't rely on default layout for FFI.
struct BadFFI { x: i32, y: i32 }  // no repr                    // ❌

// Don't take references to packed fields.
let r = &packed.field;                                             // ❌ E0793

// Don't combine repr(packed) with repr(align).
#[repr(packed, align(16))]                                         // ❌

// Don't put Drop types in packed structs.
#[repr(packed)]                                                    // ❌
struct Bad { s: String }

// Don't assume repr(Rust) offsets are stable across compilers.
offset_of!(DefaultStruct, field)  // not stable                    // ❌

// Don't dereference a pointer to an unaligned field normally.
let x = unsafe { *ptr };  // UB if unaligned                         // ❌

Common Pitfalls

PitfallWhy It HappensFix
Reference to packed field&p.field violates alignment invariantUse by-value access or read_unaligned
FFI struct has wrong layoutDefault repr(Rust) reorders fieldsAdd #[repr(C)]
Size larger than expectedrepr(C) preserves order, introducing paddingReorder fields or accept the padding
Cache-line contentionAdjacent atomics share a line#[repr(align(64))] on the wrapper
Compile error E0793Packed field referencedReplace &p.field with p.field
Drop type in packed structDestructor needs a referenceRemove the Drop type or the packing
Alignment assertion failsrepr(align) not applied or wrong powerCheck attribute and N value

Real-World Examples

1. FFI Struct

#[repr(C)]
pub struct timespec { pub tv_sec: i64, pub tv_nsec: i64 }

2. Network Header

#[repr(C, packed)]
struct IpHeader { version_ihl: u8, tos: u8, len: u16 }

3. Cache-Line Padded Atomic

#[repr(align(64))]
struct Counter(std::sync::atomic::AtomicU64);

4. Reordered for Size

#[repr(C)]
struct Tight { a: u8, b: u8, c: u32 } // 8 bytes, not 12

5. Reading a Packed Field

let ptr = std::ptr::addr_of!(h.len);
let len = unsafe { ptr.read_unaligned() };

6. SIMD Alignment

#[repr(C, align(32))]
struct AvxVector([f32; 8]);

7. Union with repr(C)

#[repr(C)]
union Value { i: i32, f: f32 }

8. Offset Assertion

assert_eq!(offset_of!(Header, len), 4);

9. Size Assertion

assert_eq!(size_of::<CPacket>(), 12);

10. Alignment Assertion

assert_eq!(align_of::<CacheLine>(), 64);

Visual

The repr(C) Padding Algorithm

┌─────────────────────────────────────────────────────────┐
│  struct Header { tag: u8, len: u32, flags: u8 }         │
│  #[repr(C)]                                             │
│                                                         │
│  Step 1: tag (size 1, align 1)                          │
│  ┌───┐                                                  │
│  │tag│  offset 0                                        │
│  └───┘                                                  │
│                                                         │
│  Step 2: len (size 4, align 4)                          │
│  ┌───┬───┬───┬───┬───┬───┬───┬───┐                     │
│  │tag│pad│pad│pad│len│len│len│len│                     │
│  └───┴───┴───┴───┴───┴───┴───┴───┘                     │
│   0   1   2   3   4   5   6   7                         │
│           ↑                                             │
│     3 bytes padding to align len at 4                   │
│                                                         │
│  Step 3: flags (size 1, align 1)                        │
│  ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐     │
│  │tag│pad│pad│pad│len│len│len│len│flg│pad│pad│pad│     │
│  └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘     │
│   0   1   2   3   4   5   6   7   8   9  10  11         │
│                                   ↑                     │
│                        3 bytes tail padding to align    │
│                        total size to 4 (struct align)   │
│                                                         │
│  Final: size 12, align 4                                │
└─────────────────────────────────────────────────────────┘

Packed Layout Hazard

┌─────────────────────────────────────────────────────────┐
│  #[repr(C, packed)]                                     │
│  struct Wire { tag: u8, len: u32 }                      │
│                                                         │
│  Memory (6 bytes, align 1):                             │
│  ┌───┬───┬───┬───┬───┬───┐                             │
│  │tag│len│len│len│len│???│                             │
│  └───┴───┴───┴───┴───┴───┘                             │
│   0   1   2   3   4   5                                │
│       ↑                                                 │
│  len starts at offset 1, but u32 requires align 4.      │
│                                                         │
│  &wire.len  →  pointer to address 1                     │
│               NOT a multiple of 4                       │
│               → UB if dereferenced as &u32              │
│                                                         │
│  wire.len   →  by-value read                            │
│               compiler emits unaligned load             │
│               → safe                                    │
│                                                         │
│  ptr.read_unaligned()  →  explicit unaligned access     │
│                           → safe                        │
└─────────────────────────────────────────────────────────┘

Alignment Control

┌─────────────────────────────────────────────────────────┐
│  #[repr(align(16))]                                     │
│  struct Aligned { value: u64 }                          │
│                                                         │
│  Without align:                                         │
│  ┌───────────────────────┐                              │
│  │ 0x00 │ 0x01 │ ... │0x07│  size 8, align 8            │
│  └───────────────────────┘                              │
│  Next struct may start at 0x08                          │
│                                                         │
│  With align(16):                                        │
│  ┌───────────────────────┬────────────────────────────┐│
│  │ 0x00 │ 0x01 │ ... │0x07│ padding (8 bytes)          ││
│  └───────────────────────┴────────────────────────────┘│
│  size 16, align 16                                      │
│  Next struct must start at a multiple of 16             │
│                                                         │
│  Array of 2:                                            │
│  ┌───────────────────────┬────────────────────────────┐│
│  │ instance 0 (16 bytes) │ instance 1 (16 bytes)      ││
│  └───────────────────────┴────────────────────────────┘│
│  0x00                    0x10                           │
│  Each instance on its own 16-byte boundary              │
└─────────────────────────────────────────────────────────┘

FFI Boundary Agreement

┌─────────────────────────────────────────────────────────┐
│  RUST SIDE                    C SIDE                    │
│                                                         │
│  #[repr(C)]                   struct point {            │
│  struct Point {                 int32_t x;              │
│      x: i32,                    int32_t y;              │
│      y: i32,                  };                        │
│  }                                                      │
│                                                         │
│  Both compilers follow the same rules:                  │
│    offset(x) = 0                                        │
│    offset(y) = 4                                        │
│    size = 8                                             │
│    align = 4                                            │
│                                                         │
│  The layouts match. The call is sound.                  │
│                                                         │
│  WITHOUT repr(C):                                       │
│    Rust may reorder y before x, or add padding.         │
│    The C side has no way to know.                       │
│    The call is undefined behavior.                      │
└─────────────────────────────────────────────────────────┘

Summary

ItemValue
repr(Rust)Default, unspecified field order and padding
repr(C)Declaration order, C ABI layout, FFI-safe
repr(align(N))Raises alignment to at least N (power of two)
repr(packed)Removes padding, alignment becomes 1
repr(C, packed)Packed with C field order, FFI wire format
Size algorithmSequential placement with alignment rounding
offset_of!Stable field offset query for repr(C) types
E0793Error for reference to packed field
read_unalignedSafe unaligned load through raw pointer
Drop in packedForbidden, destructor needs a reference

Key takeaways:

  • repr(C) is for predictability, not portability. It makes the layout match the target’s C ABI. It does not make the type safe to send anywhere.
  • The size algorithm is deterministic. Fields are placed in order, padded to alignment, and the total is rounded to the struct’s alignment. offset_of! and size_of! confirm the result.
  • Reordering is a manual optimization under repr(C). Moving small fields together can eliminate padding, at the cost of declaration order.
  • repr(align(N)) raises both alignment and size. The size rounds up to a multiple of N, which is why cache-line padding consumes a full line per value.
  • Packed structs trade safety for size. Every field access is by value or through an explicit unaligned operation. References to fields are undefined behavior.
  • E0793 is a correctness check, not an inconvenience. It prevents the compiler from creating a reference that violates its own alignment invariant.
  • repr(packed) forbids Drop types. The destructor would receive an unaligned reference, which is unsound.
  • FFI types need repr(C) and matching field types. Layout agreement requires both compilers to use the same sizes and alignments for primitives.
  • Alignment is hardware-relevant. Unaligned SIMD loads may fault. False sharing may serialize concurrent writes. repr(align) addresses both.
  • Default layout is unstable. Do not write code that depends on repr(Rust) offsets. Use repr(C) for any layout-dependent logic.

Remember: Memory layout attributes are a contract between the programmer and the compiler about how bytes are arranged. repr(C) promises C compatibility, repr(align) promises a minimum boundary, and repr(packed) promises no wasted space. Each promise has a cost: repr(C) gives up reordering, repr(align) gives up compactness, and repr(packed) gives up alignment safety. The attributes exist because sometimes the cost is worth paying—for FFI, for hardware, for protocols—but none of them should be applied without understanding which problem it solves and what it prevents the compiler from doing.


Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!