Type System Fundamentals

Structs and Enums as User-Defined Types

struct and enum declarations are the primary tools for modeling domain concepts as first-class types in Rust, combining data representation, visibility encapsulation, and behavior (impl blocks).

Three Forms of struct and Three Forms of Variant Payloads

Both struct definitions and individual enum variants support the same three field syntaxes:

Form struct Syntax enum Variant Syntax Typical Use Case
Named fields struct User { id: u64, active: bool } Event::Click { x: i32, y: i32 } Multi-field domain records with self-documenting field names
Tuple fields struct Meters(pub f64); Event::KeyPress(char) Single-field newtypes, coordinates Point(i32, i32), wrapper variants
Unit (fieldless) struct Marker; (0 bytes) Event::Quit Zero-sized type markers, state tokens, C-like enum tags

Enforcing Construction Invariants Through Module Privacy

While an enum's variants are always public whenever the enum itself is pub, a struct's fields are private to its defining module by default—even when the struct is declared pub struct.

Keeping a struct's fields private and exposing a validated constructor (pub fn new(...) -> Result<Self, Error>) plus read-only getters guarantees that no code outside the module can ever construct or mutate an invalid instance:

mod domain {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct Percentage(u8); // Private inner field `0`!

    impl Percentage {
        pub fn new(value: u8) -> Result<Self, &'static str> {
            if value <= 100 {
                Ok(Self(value))
            } else {
                Err("percentage must be between 0 and 100")
            }
        }

        pub const fn get(self) -> u8 {
            self.0
        }
    }
}

fn main() {
    let valid = domain::Percentage::new(85).unwrap();
    let invalid = domain::Percentage::new(120);

    // `domain::Percentage(150)` would fail to compile here because field `0` is private!
    println!("Valid:   {}% ({valid:?})", valid.get());
    println!("Invalid: {invalid:?}");
}
Valid:   85% (Percentage(85))
Invalid: Err("percentage must be between 0 and 100")

Modeling State Transitions with Structs and Enums

Combining structs (which enforce required fields for a given state) inside an enum (which enforces that a system is in exactly one state at a time) eliminates runtime flags and optional-field checks:

struct QueuedJob {
    id: u64,
}

struct RunningJob {
    id: u64,
    worker_thread: u32,
}

enum JobLifecycle {
    Queued(QueuedJob),
    Running(RunningJob),
}

impl QueuedJob {
    fn start(self, worker_thread: u32) -> RunningJob {
        RunningJob {
            id: self.id,
            worker_thread,
        }
    }
}

fn main() {
    let job = QueuedJob { id: 42 };
    let running = job.start(3);
    let state = JobLifecycle::Running(running);

    if let JobLifecycle::Running(r) = state {
        println!("Job #{} is running on worker {}", r.id, r.worker_thread);
    }
}
Job #42 is running on worker 3