liveBook · Manning

Marker trait

As explained in Write Powerful Rust Macros

A marker trait is a trait with no methods or associated properties that classifies implementing types.

A marker trait has no methods or properties. Its purpose is classification: empty structs implement it so a generic type can represent a valid compile-time state without storing a runtime value for that state. section 8.5.1

In a type-state design, constrain the generic parameter with the marker trait and carry it with `PhantomData<T>`. Then expose different `impl` blocks for each state. A `TrafficLight<Green>` can turn red, while a `TrafficLight<Red>` can turn green; neither type offers an invalid repeated transition. section 8.5.1

Traffic light states

```rust trait Light {} struct Green {} struct Red {} impl Light for Green {} impl Light for Red {} struct TrafficLight<T: Light> { marker: PhantomData<T> } impl TrafficLight<Green> { fn turn_red(&self) -> TrafficLight<Red> { /* ... */ } } impl TrafficLight<Red> { fn turn_green(&self) -> TrafficLight<Green> { /* ... */ } } ``` The returned type records the next state, so method availability enforces the state machine at compile time. section 8.5.1

The same approach can make a builder require fields in sequence. Generate an empty field-state struct for every required field, implement a builder marker trait for each one, and return a builder specialized to the next state from every setter. Only the final state receives `build`. section 8.5.2

Required builder fields

A builder begins with the first field-state type. Calling `roots_of` returns the `breath_of_a_fish` state; subsequent setters advance it until `FinalBuilder`. Calling `build` early fails because that method exists only for `Builder<FinalBuilder>`. section 8.5.2

Listing 8.20 Traffic light
trait Light {}                #1

struct Green {}             
struct Red {}                

impl Light for Green {}      
impl Light for Red {}       

struct TrafficLight<T: Light> { #2
    marker: PhantomData<T>
}                            

impl TrafficLight<Green> { #3
    fn turn_red(&self) -> TrafficLight<Red> {
        TrafficLight {
            marker: Default::default(),
        }
    }
}                             

impl TrafficLight<Red> {
    fn turn_green(&self) -> TrafficLight<Green> {
        TrafficLight {
            marker: Default::default(),
        }
    }
}                           

fn main() {
    let light = TrafficLight { marker: Default::default() };
    light.turn_red().turn_green();     #4
}

Inspect the minimal traffic-light implementation: the empty trait and structs classify state, `PhantomData` carries the generic state, and specialized implementations permit only alternating transitions. section 8.5.1

How Write Powerful Rust Macros covers it

How deep it goes
Works through marker traits, empty state structs, generic bounds, PhantomData, specialized implementations, generated builder states, and compile-fail testing.
Examples
Uses Green and Red traffic lights, a Gleipnir builder, generated field-state structs, a successful builder test, and a missing-property compile test.
What it assumes
Assumes intermediate Rust and applies traits, generics, implementation blocks, and PhantomData to type-state designs.
What you can do after
Build it from scratch to encode state transitions and restrict build methods to completed builder states.
How it connects
Connects marker traits to the type state pattern, PhantomData, generic parameters, builder pattern, procedural macros, and compile-time errors.

Also explained in

Compare how 2 books explain marker trait →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Write Powerful Rust Macros. The section links point to the parts of the book it draws on.