Skip to content

← Performance and Data Layout step 4 of 20

Medium Primitives

Shrink an enum and inspect its layout

Reduce Message's inline size by storing its 512-byte Blob payload in a Box. Update the Blob construction in described, but keep describe unchanged and preserve the Ping and Code variants. Retain both compile-time assertions:

size_of::<Message>() <= 16
size_of::<Option<Message>>() == size_of::<Message>()

The supplied layout_report is already filled in. Keep it reporting actual size_of and align_of results; do not replace them with hardcoded values.

Preserve the descriptions and report

described takes code: u32 and fill: u8 and returns these three strings in order:

  • "ping"
  • "code {code}", with decimal code and no padding
  • "blob 512 first={fill}", with decimal fill and no padding

Construct a Blob containing 512 copies of fill. All u32 code values and u8 fill values are valid. Test JSON uses those same numeric ranges.

layout_report returns (name, size, alignment) for these eight types in order. Use exactly the displayed type text as each name:

Message
Option<Message>
Box<[u8; 512]>
Option<Box<[u8; 512]>>
&u8
Option<&u8>
u64
Option<u64>

Sizes and alignments are in bytes. The wrapper reports the names, descriptions and checks on size relationships and power-of-two alignments. Those checks do not replace the requirement to report each type's real layout.

Why boxing changes the inline size

An enum value must have space for whichever variant it holds. The starter's inline array makes Message large even when the active variant is Ping. The exact size includes representation and alignment decisions; it is not universally the largest payload plus a fixed-width tag.

Boxing moves the array into separately allocated storage. Message then holds the Box handle for that variant. The existing b.len() and b[0] expressions in describe still work through dereferencing, so its body need not change.

size_of::<Message>() measures inline storage, not the total memory owned by a value. It excludes the boxed array and allocator overhead. A Blob pays for its allocation and indirection; Ping and Code need no boxed payload. Smaller inline elements can improve collection density, but this tradeoff does not guarantee a runtime speedup. A source-level move also does not imply that the compiler emits a copy of every byte.

What a niche is

A niche is an otherwise-invalid representation a compiler can use to encode another state. For example, a valid reference cannot be null. Option<&u8> can use the null representation for None without adding storage.

Rust documents equal size and alignment for Option<&u8> and &u8, and for Option<Box<[u8; 512]>> and Box<[u8; 512]> using the standard allocator. These sized-pointee cases are covered by the Option representation guarantees.

This complete example checks those relationships without assuming a pointer is eight bytes:

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

fn main() {
    assert_eq!(size_of::<Option<&u8>>(), size_of::<&u8>());
    assert_eq!(align_of::<Option<&u8>>(), align_of::<&u8>());
    assert_eq!(size_of::<Option<Box<[u8; 512]>>>(), size_of::<Box<[u8; 512]>>());
    assert_eq!(align_of::<Option<Box<[u8; 512]>>>(), align_of::<Box<[u8; 512]>>());
}

u64 uses every bit pattern for a value, so Option<u64> needs a distinct way to represent None. The report checks that it is larger than u64 on the grading target; do not assume a universal 16-byte layout or a fixed tag offset.

Distinguish guarantees from build checks

The custom Message enum uses Rust's default representation. The compiler may exploit unused representations so Option<Message> has the same size, but this particular equality is not one of Option's documented guarantees for arbitrary user-defined enums. Treat it as a requirement checked on the compiler and target used to build this exercise.

Default Rust layout still has basic guarantees, including proper field alignment and non-overlapping fields. It does not promise a particular field order or exact enum tag arrangement. Consult the layout reference before relying on more than a measured size.

A failing const assertion produces E0080 during compilation. It proves that the stated condition fails for that build; a passing assertion does not turn an implementation choice into a language guarantee. The starter's large inline payload fails the size limit. Do not assume both assertions must fail.

Keep the assertions and use the boxed representation to satisfy them on the grading toolchain. Passing also requires the expected descriptions/report checks, successful compilation and Clippy with warnings treated as errors.