Rust: Structs, Enums & Pattern Matching
What you will learn
How to define custom data types with structs and enums, and how match makes exhaustive pattern checking natural.
Defining a struct
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
let user1 = User {
username: String::from("alice"),
email: String::from("[email protected]"),
sign_in_count: 1,
active: true,
};
println!("{}", user1.email); // dot notation
Methods on structs
Methods are defined in an impl block:
impl User {
fn is_active(&self) -> bool {
self.active
}
fn update_email(&mut self, new_email: String) {
self.email = new_email;
}
}
&self borrows the struct immutably. &mut self borrows mutably. self takes ownership (rare, for destructors).
Enums
Enums can hold data:
enum Message {
Quit,
Move { x: i32, y: i32 }, // struct-like
Write(String), // tuple-like
ChangeColor(i32, i32, i32),
}
let msg = Message::Move { x: 10, y: 20 };
Pattern matching with match
fn process(msg: Message) {
match msg {
Message::Quit => println!("Quitting"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Message: {}", text),
Message::ChangeColor(r, g, b) => println!("Color RGB({},{},{})", r, g, b),
}
}
match is exhaustive -- you must handle every variant. The compiler enforces this.
The Option enum (no null)
Rust has no null. Instead:
enum Option<T> {
None,
Some(T),
}
fn maybe_length(s: Option<String>) -> usize {
match s {
Some(val) => val.len(),
None => 0,
}
}
Common mistakes
- Forgetting to handle a variant in
match-- the compiler catches this, which is a feature, not a bug - Using
matchwhenif letis simpler:if let Some(v) = val { ... }for single-variant matching - Not implementing
Debugon structs (#[derive(Debug)]) before trying to print them
Quick check below!