03open 25 min

Data and state modeling: enums and match instead of struct-plus-switch habits

Use enums and pattern matching to model explicit state machines more directly than typical Go code.

by the end of this lesson you can

  • Uses an enum instead of a string tag
  • Carries data on the relevant variant only
  • Uses match instead of ad hoc branching

Overview

Go developers often represent variants with structs, interfaces, constants, or booleans plus switch. Rust gives you enums that carry data and match expressions that force you to handle each state deliberately.

In Go, you often

model states with a struct plus flags, sentinel values, or interface-based branching that relies on convention.

In Rust, the common pattern is

to model variants directly as enum cases and use match to make branching exhaustive and visible.

why this difference matters

This is one of the clearest places where Rust's type system helps you express legal states instead of merely documenting them.

Go

type Job struct {
    Status string
    Error  string
}

Rust

enum Job {
    Queued,
    Running,
    Failed(String),
    Done,
}

Deeper comparison

Go version

switch job.Status {
case "queued":
    run(job)
case "failed":
    log.Println(job.Error)
}

Rust version

match job {
    Job::Queued => run(job_id)?,
    Job::Failed(message) => println!("{}", message),
    Job::Running => {}
    Job::Done => {}
}

Reflect

What gets safer when valid states are encoded directly rather than implied by string values or boolean combinations?

what a strong answer notices

A strong answer mentions fewer impossible states, more complete branching, and clearer state transitions.

Rewrite

Rewrite this Go tagged-state example into a Rust enum plus match.

Rewrite this Go

type Response struct {
    Kind string
    Body string
}

if res.Kind == "error" {
    log.Println(res.Body)
}

what good looks like

  • Uses an enum instead of a string tag
  • Carries data on the relevant variant only
  • Uses match instead of ad hoc branching

Practice

Design a Rust enum for an HTTP request lifecycle with pending, validated, rejected, and handled states.

success criteria

  • Represents each state explicitly
  • Carries data only where it belongs
  • Shows how match would keep handling exhaustive

Common mistakes

  • Recreating Go string-status patterns in Rust.
  • Using enums only as named constants instead of data-carrying variants.
  • Treating match as verbose control flow instead of part of the model.

takeaways

  • This is one of the clearest places where Rust's type system helps you express legal states instead of merely documenting them.
  • A strong answer mentions fewer impossible states, more complete branching, and clearer state transitions.
  • Represents each state explicitly