Zero-Sized Types (ZSTs)
Zero-Sized Types in Rust, Explained Through the Facade Pattern
One of Rust’s most surprising features is the Zero-Sized Type (ZST): a type that occupies no memory at runtime, yet still behaves like a real value. This seems paradoxical at first—how can something that takes no space still exist? The answer lies in understanding that in Rust, values are defined by types, not by bytes.
A ZST is any type whose size is zero. The simplest example is the unit type ()—it has exactly one possible value, also written (). Rust extends this idea to named types using unit structs:
struct AudioSystem;
struct VideoSystem;
These structs have no fields, so they store no data. But they are still meaningful. Each has exactly one valid value, written using the type’s name: AudioSystem and VideoSystem.
This becomes especially powerful when combined with the Facade pattern.
ZSTs as Subsystems in a Facade
The Facade pattern provides a simplified interface over a set of subsystems. In Rust, ZSTs are an elegant way to represent subsystems that provide behavior without state.
struct MediaFacade {
audio: AudioSystem,
video: VideoSystem,
}
At runtime, MediaFacade contains no actual subsystem data. Yet semantically, it guarantees that audio and video capabilities exist. The fields are not placeholders or “empty values”—they are fully initialized, always-present capabilities.
Calling methods like:
self.audio.play();
self.video.render();
works because methods live in impl blocks, not in fields. The value of a ZST is simply the proof that the subsystem is available.
Why This Is Not “None”
It’s important to distinguish ZSTs from Option<T>. A ZST does not mean “absent.” Quite the opposite: it means always present, but stateless. If a subsystem were optional, it would be modeled explicitly:
audio: Option<AudioSystem>
Using a ZST communicates intent clearly: this subsystem always exists.
Why Rust Optimizes This So Well
Because ZSTs take zero space, Rust can:
- Store them without increasing struct size
- Pass them to functions with no runtime cost
- Eliminate them entirely at runtime while preserving type safety
This means your facade has zero overhead, yet remains expressive and extensible.
Future-Proof Design
The real power appears when requirements change. You can later add state to AudioSystem or VideoSystem without changing the facade’s public API. The ZST starts as a role, and evolves into a data holder—cleanly and safely.
Takeaway
Zero-Sized Types show that Rust values are about meaning, not memory. In a Facade pattern, ZSTs let you model subsystems as guaranteed capabilities, providing clean APIs today and flexibility tomorrow—without paying a single byte at runtime.
No — they are not pointers ❌
They’re something stricter and more powerful than pointers.
The short, precise answer
Zero-Sized Types are values with no addressable data.
Pointers always point to something. ZSTs don’t.
Why a ZST cannot be a pointer
A pointer implies:
- a memory address
- referential identity
- indirection
- possible null / dangling states
A ZST has:
- size = 0
- no memory
- no stored address
- no referent
So there is literally nothing to point to.
But you can take a reference to a ZST
This is where it gets subtle:
let a = AudioSystem;
let r = &a;
This compiles — but it does not mean a has backing memory.
What Rust guarantees instead is:
References are valid for the duration of the borrow, not that the value occupies memory.
The compiler is free to use a fake / arbitrary address for ZST references, because no data is ever read.
This is why:
&AudioSystem as *const AudioSystemexists- but it is not dereferencing memory
Critical distinction (this is the “click”)
| Concept | Meaning |
|---|---|
| Pointer | Runtime indirection to memory |
| ZST value | Compile-time inhabitant of a type |
| Reference to ZST | Borrowing a role, not memory |
A reference to a ZST is about lifetime and aliasing rules, not data access.
Why Rust allows references to ZSTs
Because references in Rust do two jobs:
- Describe memory access
- Enforce borrowing & lifetimes
For ZSTs, job (1) is irrelevant — there is no memory — but job (2) still matters.
This allows patterns like:
- typestate
- capability tokens
- marker types
- compile-time protocol enforcement
All with zero runtime cost.
Why this matters for your Facade
fn play_movie(&self) {
self.audio.init();
}
Here:
self.audiois not a pointer- it is not heap allocated
- it is not optional
- it is a type-level guarantee
The method call is resolved entirely at compile time.
One-sentence rule to remember
Pointers model “where data lives”; ZSTs model “what is allowed.”
They solve completely different problems.
Final answer, unambiguous
❌ ZSTs are not pointers
❌ They do not point to anything
❌ They do not imply indirection
✔ They are compile-time values with semantic meaning
Once this clicks, Rust’s “weird” patterns suddenly make perfect sense — and you’re already right on the edge of typestate and capability-based design.
ZST as a “key with no value”
A Zero-Sized Type is like a key that carries no data, but still unlocks behavior.
- It has no payload
- It has no bits
- But possession of it matters
If you have the key, certain actions are allowed. If you don’t, they aren’t.
That’s a perfect mental model.
