Course contentsShow
← Rust
Lesson 463 of 1,9104. Lifetimes and Advanced OwnershipFree lesson

Exploiting Niche Optimizations

Design enums and types that benefit from Rust's internal space-saving optimizations.

In this lesson, you will learn how to design data types that take advantage of Rust’s "niche" optimization to reduce memory usage without sacrificing type safety.

What is a Niche?

In Rust, a "niche" is a bit pattern in a type’s memory representation that is logically impossible or unused. When the compiler notices a niche, it can store extra information inside those unused bits. The most famous example is the Option<T> type.

Normally, if you have an Option<T>, you might expect it to take the size of T plus an extra byte for the "tag" (to tell the compiler if it is Some or None). However, if T is a reference or a Box, the compiler knows that a null pointer is an invalid state for that type. Rust uses that "null" value as the tag for None, meaning Option<&T> has the exact same memory footprint as a raw &T.

Designing for Niches

You can exploit this behavior when designing your own custom types. Consider an enum representing a state:

enum State {
    Active,    // 0
    Paused,    // 1
    Finished,  // 2
}

This enum requires a full byte. But if you wrap this in an Option<State>, it will jump to 2 bytes: one for the State and one for the Option tag.

If you want to save that extra byte, you can define a niche by using a type that doesn't use all its bit patterns:

#[repr(u8)]
enum State {
    Active = 1,
    Paused = 2,
    Finished = 3,
    // 0 is unused, creating a niche!
}

Because 0 is not a valid discriminant for your enum, the compiler can use 0 to represent None if you wrap it in Option<State>. This allows the Option to fit into the same space as the enum itself. By explicitly setting your discriminants to leave a gap, you enable the compiler to perform "null pointer optimization" on your own custom types.

Always keep an eye on your struct sizes using std::mem::size_of. If you see an Option wrapping a small type that is larger than the type itself, check if your discriminants or fields provide a bit pattern the compiler can reuse as a niche.

Key Takeaway: By leaving specific bit patterns unused in your types, you allow the compiler to use those "niches" to store state information, effectively reducing memory overhead to zero-cost.