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 animplblock. Selfin a signature is shorthand for the type being implemented, which keeps refactors to a single line.- The first parameter decides the receiver:
&selfborrows,&mut selfborrows mutably, and noselfmakes it an associated function called asTask::new(...). ..otheris 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);
}| Combinator | What it does |
|---|---|
map | Transforms 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_then | Chains 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.
Related
Syntax and ownership Error handling with Result and Cargo
Last refreshed 2026-09-18.