CodeBlanks Logo Icon CodeBlanks

Niche optimization

Shrink enum size with niche optimization.

Niche optimization

Some types have bit patterns that are never used: &T and Box<T> are never null, bool is only 0 or 1, NonZeroU32 can't be 0, and char lies outside certain ranges. The compiler uses these "niches" to store the enum discriminant, so Option needs no extra byte:

size_of::<Option<&u8>>()        // 8: None = null pointer
size_of::<Option<NonZeroU32>>()  // 4: None = 0
size_of::<Option<u32>>()         // 8: u32 has no unused pattern
size_of::<Option<bool>>()        // 1: None = 2

A niche in your own type

Holding ids as NonZeroU32 lets you express the "invalid id" case with Option for free:

use std::num::NonZeroU32;

struct KullaniciId(NonZeroU32);

let id = NonZeroU32::new(42).map(KullaniciId);   // Some
let bos = NonZeroU32::new(0);                     // None
println!("{}", size_of::<Option<KullaniciId>>()); // 4

Niches work when nested too: Option<Option<bool>> is still 1 byte.

Practice Niche sizes

Print the size_of values of Option<&u8>, Option<NonZeroU32>, Option<u32> and Option<bool> on one line separated by spaces.

Starter code

main.rs
use std::mem::size_of;
use std::num::NonZeroU32;

fn main() {
    // buraya yaz
}

Sample solution

main.rs
use std::mem::size_of;
use std::num::NonZeroU32;

fn main() {
    println!(
        "{} {} {} {}",
        size_of::<Option<&u8>>(),
        size_of::<Option<NonZeroU32>>(),
        size_of::<Option<u32>>(),
        size_of::<Option<bool>>()
    );
}

Expected output

8 4 8 1

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 Why can <code>Option&lt;Box&lt;T&gt;&gt;</code> be used in FFI in place of C's nullable pointer?

  • Box exists in C
  • It can't be
  • It's always 16 bytes
  • None is guaranteed to be represented as a null pointer Correct answer

Explanation: This is one of a few documented niche guarantees: Option<&T>, Option<Box<T>>, Option<NonNull<T>>, Option<extern fn>.

2 Non-zero integer

Question
use std::num::____;
let id = NonZeroU32::new(42);   // Some(42)
Answer
use std::num::NonZeroU32;
let id = NonZeroU32::new(42);   // Some(42)

Explanation: NonZeroU32 can't be 0, so Option<NonZeroU32> is 4 bytes.

3 Type and size (64-bit)

Term Meaning
Option<&u8> 8
Option<NonZeroU32> 4
Option<u32> 8 (extra tag)
Option<bool> 1

All lessons in Expert