On this page
Primitive Types and Memory Layout
Every type in Rust has a precise memory layout—integers and floats are aligned by bit width, structs have padding, zero-sized types (ZSTs) occupy 0 bytes, and niche optimization makes
Option<bool>the same size asbool(since bool only has 0/1, the compiler uses 2 bit patterns to represent None). Understanding layout is the prerequisite for understanding why Rust has no "boxing overhead".
Scalar Types: More Than Just "Like C"
Rust's integer types look like C's—i32, u64, etc.—but there are important semantic differences:
let x: u8 = 255;
// x + 1: debug mode → panic!("attempt to add with overflow")
// release mode → wraps to 0 (two's complement, no undefined behavior)
// Three explicit overflow handling methods:
255u8.checked_add // None (returns Option, does not panic)
255u8.wrapping_add // 0 (explicitly wants to wrap)
255u8.saturating_add // 255 (saturating arithmetic)
In C, signed integer overflow is UB. In Rust, even wrapping in release mode is defined behavior—it is not UB. However, the panic in debug mode helps you expose overflow bugs during testing.
Type Size and Alignment
Each type has a deterministic size_of and align_of, independent of the platform (except for isize/usize):
use ;
// 1
// 8
// 1 (not 1 bit! The minimum addressable unit of memory is a byte)
// 4 (Unicode scalar value, not code point)
// 0 (unit type, ZST)
// 8 (64-bit) or 4 (32-bit)
The reason bool occupies 1 byte is that the minimum addressable unit of CPU instruction sets is a byte. Rust does not allow taking the address of a single bit within a bool. If you need compact storage for boolean arrays, use the bitvec crate or manually bit-pack using Vec<u64>.
char: 4 bytes = One Unicode Scalar Value
let c: char = 'あ'; // U+3042, 3 bytes in UTF-8, 4 bytes as char
// 4
// Relationship between char and string:
let s = "あ"; // &str, 3 bytes: [E3, 81, 82]
let c = 'あ'; // char, 4 bytes: 0x00003042
char represents a Unicode scalar value (0x0000~0xD7FF, 0xE000~0x10FFFF), not "a character" (a user-perceived character might be composed of multiple scalar values, such as é = e + ́). This is similar to Go's rune (=int32).
Memory Layout of Composite Types
// memory: [a:1B][padding:3B][b:4B][c:2B][padding:2B] = 12 bytes
// C ABI: fields are in declaration order, with automatic padding to satisfy alignment requirements
// Default layout: the compiler may reorder fields to optimize size!
// Possible arrangement: [b:4B][c:2B][a:1B][padding:1B] = 8 bytes
The default Rust layout does not guarantee order—the compiler may reorder fields to reduce padding. If you need interoperability with C or a guaranteed stable layout, you must use #[repr(C)].
Arrays: Size Is Part of the Type
let arr1: = ; // Fixed size at compile time
let arr2: = ; // 4000 bytes on the stack!
// Passed by value: copies the entire 4000 bytes
// Passed by reference: only copies the fat pointer (16 bytes)
Passing an array by value copies the entire array—because Rust's move semantics perform element-wise copying for arrays. For large arrays, always pass &[T] or &[T; N].
ZST (Zero-Sized Types): Invisible Magic at Compile Time
; // 0 bytes
let x = NoData; // Occupies no stack space
let arr: ; // 0 bytes! No memory is allocated
// Practical value of ZST: HashMap keys
use HashSet;
let set: = ...; // Distinguishes only between "present" and "absent"
The compiler completely omits the storage and passing of ZSTs when generating code—they do not exist anywhere at runtime, having meaning only at the type level.
Niche Optimization: Zero-Cost Option
The Rust compiler leverages "impossible bit patterns" to save enum space:
// 1 byte, not 2!
// bool has only two valid values: 0x00 (false) and 0x01 (true)
// The compiler uses 0x02 to represent None → no extra discriminant field needed
// 8 bytes, not 16!
// &T is guaranteed to be non-null → the compiler encodes None as nullptr
// When dereferencing Option<&T>: first check if null → None → panic/return None
// → Some is just a normal reference
// 8 bytes — Box internally uses a non-null ptr
// 8 bytes — two niches: nullptr and ...?
// Actually, Option<&T> has only one niche (null)
// Option<Option<&T>> requires 2 niche values → 16 bytes (two words)
This optimization is key to Rust maintaining zero overhead in systems-level safety—you pay no extra memory or runtime cost for Option<&T>.
References
- Rust Reference: Type Layout (section "Data Layout")
- Rustonomicon: Exotic Sizes, repr, alignment
- RFC: RFC 139 (niche optimization)
Keywords: integer types, alignment, repr(C), ZST, niche optimization, Option<&T>, layout, size_of, align_of