Rust Type Sizes
A type's size is the number of bytes one value of it takes. The size of a struct or enum decides how much memory it uses and how fast it copies.
Measure any type
size_of answers in bytes:
println!("u8 = {}, u32 = {}, &u8 = {}", size_of::<u8>(), size_of::<u32>(), size_of::<&u8>());Padding between fields
A u32 must start at a multiple of 4, so a byte before it leaves 3 wasted bytes. Pin the field order with #[repr(C)] to see it:
#[repr(C)]
struct Mixed { a: u8, b: u32, c: u8 }
println!("repr(C) mixed order = {}", size_of::<Mixed>());Box the big variant
This enum is 101 bytes because the tag has to fit beside the big variant. A box moves the bulk to the heap:
enum Big { Small(u8), Large([u8; 100]) }
enum BigBoxed { Small(u8), Large(Box<[u8; 100]>) }
println!("plain = {}, boxed = {}", size_of::<Big>(), size_of::<BigBoxed>());The tag that costs nothing
A Box is 8 bytes. Option<Box<u32>> is also 8. The tag hides in the address 0. A Box can never hold that address:
println!("Option<Box<u32>> = {}", size_of::<Option<Box<u32>>>());size_of_val weighs one value
size_of names a type. size_of_val measures the value you hand it. A &str value is the text itself, so it weighs 6 here. The handle that points at the text weighs 16:
let text: &str = "ferris";
println!("the text = {}", std::mem::size_of_val(text));
println!("the handle = {}", std::mem::size_of_val(&text));Example
use std::mem::size_of;
fn main() {
println!("bool = {}", size_of::<bool>());
println!("u8 = {}", size_of::<u8>());
println!("u32 = {}", size_of::<u32>());
println!("char = {}", size_of::<char>());
println!("&u8 = {}", size_of::<&u8>());
println!("Box<u8> = {}", size_of::<Box<u8>>());
println!("Vec<u8> = {}", size_of::<Vec<u8>>());
println!("String = {}", size_of::<String>());
println!("Option<u32> = {}", size_of::<Option<u32>>());
println!("Option<&u8> = {}", size_of::<Option<&u8>>());
}Key points
- Use std::mem::size_of::<T>() to measure a type in bytes
- size_of_val measures one value instead of a type
- Alignment: a type must start at an address that is a multiple of its alignment
- A u32 aligns to 4
- A u64 aligns to 8
- Ordinary Rust structs get their fields reordered by the compiler to waste less space
- #[repr(C)] pins the written order, padding included
- An enum costs the size of its biggest variant, plus room for the tag that says which variant it is
- usize and isize are pointer-sized: 8 bytes on a 64-bit machine, so lengths and indexes cost one word each
- A u8 fits in one byte and a u32 needs four.
- Every value takes a fixed number of bytes, and that number is its type's size.
In this example, the sizes of Rust's building blocks are printed on a 64-bit machine. A char is 4 bytes because it is a full Unicode scalar value. A reference and a Box are both one pointer — 8 bytes. A Vec and a String are three words (pointer, length, capacity): 24 bytes. And the niches show their power twice. Option<u32> needs 4 extra bytes for the tag. Option<&u8> costs no extra at all.
Note
Sizes here are for 64-bit computers, the common case today
On a 32-bit machine every pointer-sized value — &, Box, Vec, String — is 4 bytes instead of 8
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
An enum has variants holding a u8 and a [u8; 100]. Roughly how big is it, and what shrinks it?