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 AudioSystem exists
  • but it is not dereferencing memory

Critical distinction (this is the “click”)

ConceptMeaning
PointerRuntime indirection to memory
ZST valueCompile-time inhabitant of a type
Reference to ZSTBorrowing 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:

  1. Describe memory access
  2. 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.audio is 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.