Rust: Unit Like Struct

2024-07-03

A unit-like struct in Rust is a type of struct that has no fields. These are declared using struct Name;. Despite not storing any data, unit-like structs can serve several important purposes:

Key Uses of Unit-like Structs

Type Safety and Strong Typing:

Unit-like structs can be used to create distinct types that ensure type safety. This can be particularly useful for defining custom types that represent specific states or modes without needing to store any data.

struct Ready;
struct Processing;
struct Done;

fn handle_state(state: Ready) {
    // Perform operations for the Ready state
}

fn main() {
    let state = Ready;
    handle_state(state);
}

Marker Types:

Marker types are types that do not hold any data but are used to convey information at the type level. These can be used to enforce certain behaviors or restrictions via the type system.

struct Sync;
struct Async;

trait OperationMode {}

impl OperationMode for Sync {}
impl OperationMode for Async {}

fn perform_operation<T: OperationMode>() {
    // Functionality depends on T being Sync or Async
}

fn main() {
    perform_operation::<Sync>();
    perform_operation::<Async>();
}

Zero-Sized Types (ZSTs):

Unit-like structs are zero-sized types, meaning they do not consume any memory. They can be used in situations where a type is required syntactically but no data storage is needed.

struct MyZST;

fn main() {
    let instance = MyZST;
    println!("Instance of MyZST created, but it takes no memory space.");
}

Phantom Types:

Unit-like structs are often used in conjunction with generics to create phantom types. Phantom types allow you to encode additional type information that is only used by the type system and not at runtime.

use std::marker::PhantomData;

struct Container<T> {
    _marker: PhantomData<T>,
}

impl<T> Container<T> {
    fn new() -> Self {
        Container {
            _marker: PhantomData,
        }
    }
}

fn main() {
    let _container: Container<i32> = Container::new();
}

Unit-like structs are a powerful tool in Rust for enhancing type safety, enforcing certain behaviors via the type system, and creating marker or phantom types without incurring any memory overhead. They are a key part of Rust’s robust type system and provide additional flexibility and safety in various programming scenarios.


Example: State Machine Using Unit-like Structs

Consider a simple state machine with states such as Start, Processing, and Finished. Each state can be represented by a unit-like struct:

struct Start;
struct Processing;
struct Finished;

struct StateMachine<S> {
    state: S,
}

impl StateMachine<Start> {
    fn new() -> Self {
        StateMachine { state: Start }
    }

    fn start_processing(self) -> StateMachine<Processing> {
        println!("Starting processing...");
        StateMachine { state: Processing }
    }
}

impl StateMachine<Processing> {
    fn finish_processing(self) -> StateMachine<Finished> {
        println!("Finishing processing...");
        StateMachine { state: Finished }
    }
}

impl StateMachine<Finished> {
    fn reset(self) -> StateMachine<Start> {
        println!("Resetting to start...");
        StateMachine { state: Start }
    }
}

fn main() {
    let sm = StateMachine::new();
    let sm = sm.start_processing();
    let sm = sm.finish_processing();
    let sm = sm.reset();
}

Explanation:

  • State Structs: Define unit-like structs Start, Processing, and Finished to represent different states.
  • StateMachine Struct: Define a generic StateMachine struct that holds the current state.
  • State Transitions:
    • Implement methods on StateMachine<Start> to transition to Processing.
    • Implement methods on StateMachine<Processing> to transition to Finished.
    • Implement methods on StateMachine<Finished> to transition back to Start.
  • Type Safety: Each method consumes self and returns a new StateMachine with the next state, ensuring that state transitions are explicit and type-checked at compile time.

Advantages of Using Unit-like Structs in State Machines:

  • Type Safety: By using distinct types for each state, you enforce valid state transitions at compile time, reducing runtime errors.
  • Clarity: Unit-like structs make the state transitions explicit and easy to understand.
  • Zero Overhead: Since unit-like structs are zero-sized, they do not add any memory overhead.