Structs, enums and pattern matching

Grouping data with structs, modelling alternatives with enums, and letting match prove you handled every case.

Structs and methods

#[derive(Debug, Clone, PartialEq)]
struct Task {
    title: String,
    done: bool,
    tags: Vec<String>,
}

impl Task {
    // associated function with no self: the constructor idiom
    fn new(title: &str) -> Self {
        Self {
            title: title.to_string(),
            done: false,
            tags: Vec::new(),
        }
    }

    fn complete(&mut self) {
        self.done = true;
    }

    fn summary(&self) -> String {
        let mark = if self.done { "x" } else { " " };
        format!("[{}] {}", mark, self.title)
    }
}

fn main() {
    let mut t = Task::new("write docs");
    t.complete();
    println!("{:?}", t);
    println!("{}", t.summary());
}
  • Fields are private outside the defining module unless marked pub; methods live in an impl block.
  • Self in a signature is shorthand for the type being implemented, which keeps refactors to a single line.
  • The first parameter decides the receiver: &self borrows, &mut self borrows mutably, and no self makes it an associated function called as Task::new(...).
  • ..other is the struct update syntax: it fills the remaining fields from another value of the same type.

Enums and pattern matching

enum Shape {
    Circle { radius: f64 },
    Rect { w: f64, h: f64 },
    Point,
}

fn area(s: &Shape) -> f64 {
    match s {
        Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
        Shape::Rect { w, h } => w * h,
        Shape::Point => 0.0,
    }
}

fn main() {
    let shapes = [Shape::Rect { w: 2.0, h: 3.0 }, Shape::Point];
    for s in &shapes {
        println!("{}", area(s));
    }

    let port: Option<u16> = Some(8080);
    if let Some(p) = port {
        println!("port {}", p);
    }

    let Some(p) = port else { return };   // let-else: early exit otherwise
    println!("{}", p);
}
CombinatorWhat it does
mapTransforms the success value, leaves the failure alone
unwrap_or(x)Yields the value, or a fallback when absent
ok_or(e)Turns Option into Result with an error
and_thenChains another fallible step without nesting
?Returns early from the enclosing function on failure
💡
match is exhaustive, so adding an enum variant surfaces every place that must handle it as a compile error. That is the practical reason enums beat a set of boolean flags plus parallel optional fields.

FAQ

Why do I have to derive traits?
Rust implements nothing implicitly. #[derive(Debug, Clone, PartialEq)] asks the compiler to generate the standard implementations, and it works only when every field supports them.
Struct or enum?
A struct when every instance has the same fields. An enum when an instance is one of several distinct shapes or states, especially when each shape carries its own data.

Syntax and ownership Error handling with Result and Cargo

Last refreshed 2026-09-18.