Part VII — Expert: internals and design
Parts I–VI covered how to use Rust. This Part covers how it works
underneath: what is in memory, what async compiles to, what the atomic
orderings promise, and how to design APIs that others can use safely. It
ends with two capstones that use the whole guide.
57. Memory layout: size, alignment, niches, repr(C), fat pointers
use std::mem::{align_of, size_of};
use std::num::NonZeroU32;
#[allow(dead_code)]
struct Default_ { a: u8, b: u32, c: u16 } // Rust may reorder fields
#[allow(dead_code)]
#[repr(C)]
struct CLayout { a: u8, b: u32, c: u16 } // C order, C padding
#[allow(dead_code)]
enum Shape { Circle(f32), Rect(f32, f32) }
fn main() {
println!("{:<28}{:>5}{:>7}", "type", "size", "align");
macro_rules! row { ($t:ty) => { println!("{:<28}{:>5}{:>7}", stringify!($t), size_of::<$t>(), align_of::<$t>()) } }
row!(Default_); // 8: reordered to b, c, a + 1 byte of padding
row!(CLayout); // 12: a, 3 pad, b, c, 2 pad (same as the C guide §8.1)
row!(&u8); // 8: thin pointer
row!(&[u8]); // 16: pointer + length
row!(&str); // 16
row!(&dyn std::fmt::Debug);// 16: pointer + vtable
row!(Box<u64>); // 8
row!(Option<Box<u64>>); // 8: None uses the null niche
row!(Option<u32>); // 8: needs a separate tag
row!(Option<NonZeroU32>); // 4: 0 is the niche
row!(Option<bool>); // 1: values 2..=255 are niches
row!(Result<u32, ()>); // 8
row!(Shape); // 12: tag + the larger variant
row!(String); // 24
row!(Vec<u8>); // 24
row!(()); // 0: zero-sized type
}
type size align
Default_ 8 4
CLayout 12 4
&u8 8 8
&[u8] 16 8
&str 16 8
&dyn std::fmt::Debug 16 8
Box<u64> 8 8
Option<Box<u64>> 8 8
Option<u32> 8 4
Option<NonZeroU32> 4 4
Option<bool> 1 1
Result<u32, ()> 8 4
Shape 12 4
String 24 8
Vec<u8> 24 8
() 0 1
Three rules come out of this table:
- Rust's default layout is unspecified. The compiler may reorder
fields to minimize padding.
Default_is 8 bytes, where the C layout of the same fields is 12. Use#[repr(C)]for anything that crosses an FFI boundary or is read from raw bytes, as in the C guide §8.1. Nevertransmutea default-layout struct. - Niches make
Optionfree for pointer-like types. References,Box,NonNull, andNonZero*have an invalid bit pattern (0), andNoneuses it. That is howOption<&T>has the same ABI as a nullable C pointer, so FFI code can use it. - Zero-sized types cost nothing.
HashSet<T>is aHashMap<T, ()>, and the()values take no memory. Marker types in typestate APIs (Ch 62) take no space either.