Enums Exercises
Overview
These exercises introduce Rust’s powerful enum type — far more capable than enums in most languages.
enums1 - Basic Enum Definition
Concept: Defining an enum with variants
#[derive(Debug)]
enum Message {
Resize,
Move,
Echo,
ChangeColor,
Quit,
}
fn main() {
println!("{:?}", Message::Resize);
println!("{:?}", Message::Move);
}Key Takeaways:
- Enums define a type by enumerating its possible variants
- Access variants with
EnumName::VariantName #[derive(Debug)]allows printing with{:?}
enums2 - Enums with Data
Concept: Variants can hold different types of data
#[derive(Debug)]
struct Point { x: u64, y: u64 }
#[derive(Debug)]
enum Message {
Resize { width: u64, height: u64 }, // Struct-like variant
Move(Point), // Tuple-like with struct
Echo(String), // Tuple-like with String
ChangeColor(u8, u8, u8), // Tuple-like with multiple values
Quit, // Unit variant (no data)
}
impl Message {
fn call(&self) {
println!("{self:?}");
}
}Variant Types:
| Syntax | Name | Example |
|---|---|---|
Variant { field: Type } | Struct-like | Resize { width: 10, height: 30 } |
Variant(Type) | Tuple-like | Move(Point { x: 10, y: 15 }) |
Variant(T1, T2) | Multi-value tuple | ChangeColor(200, 255, 255) |
Variant | Unit | Quit |
Usage example:
let messages = [
Message::Resize { width: 10, height: 30 },
Message::Move(Point { x: 10, y: 15 }),
Message::Echo(String::from("hello world")),
Message::ChangeColor(200, 255, 255),
Message::Quit,
];
for message in &messages {
message.call();
}Why Enums Are Powerful
Unlike C enums (just integers), Rust enums can:
- Hold different data in each variant
- Implement methods
- Be used with pattern matching (coming soon!)
🔮 Preview:
matchexpressions let you handle each variant differently. You’ll learn this in the next set of exercises!
Common Standard Library Enums
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}These are everywhere in Rust!
enums3 - Pattern Matching with match
Concept: Using match to handle enum variants and destructure their data
The Full Code
struct Point {
x: u64,
y: u64,
}
enum Message {
Resize { width: u64, height: u64 },
Move(Point),
Echo(String),
ChangeColor(u8, u8, u8),
Quit,
}
struct State {
width: u64,
height: u64,
position: Point,
message: String,
color: (u8, u8, u8),
quit: bool,
}
impl State {
fn resize(&mut self, width: u64, height: u64) {
self.width = width;
self.height = height;
}
fn move_position(&mut self, point: Point) {
self.position = point;
}
fn echo(&mut self, s: String) {
self.message = s;
}
fn change_color(&mut self, red: u8, green: u8, blue: u8) {
self.color = (red, green, blue);
}
fn quit(&mut self) {
self.quit = true;
}
fn process(&mut self, message: Message) {
match message {
Message::Resize { width, height } => self.resize(width, height),
Message::Move(point) => self.move_position(point),
Message::Echo(string) => self.echo(string),
Message::ChangeColor(red, green, blue) => self.change_color(red, green, blue),
Message::Quit => self.quit(),
}
}
}Where is Destructuring Happening?
Destructuring happens in the match arm patterns (the left side of =>):
match message {
Message::Resize { width, height } => ...
// ^^^^^^^^^^^^^^^^
// DESTRUCTURING! Extracts `width` and `height` from the variant
Message::Move(point) => ...
// ^^^^^^^
// DESTRUCTURING! Extracts the Point into `point`
Message::ChangeColor(red, green, blue) => ...
// ^^^^^^^^^^^^^^^^^
// DESTRUCTURING! Extracts all 3 u8 values
Message::Quit => ...
// No data to destructure (unit variant)
}Destructuring = extracting inner values from a data structure into named variables.
Step-by-Step Code Walkthrough
Step 1: Create Initial State
let mut state = State {
width: 0,
height: 0,
position: Point { x: 0, y: 0 },
message: String::from("hello world"),
color: (0, 0, 0),
quit: false,
};State starts with default values. It’s mut because process() will modify it.
Step 2: Process a Resize Message
state.process(Message::Resize { width: 10, height: 30 });What happens inside process():
messageisMessage::Resize { width: 10, height: 30 }matchchecks each arm from top to bottom- First arm matches:
Message::Resize { width, height } - Destructuring:
widthbecomes10,heightbecomes30 - Calls
self.resize(10, 30) - State is now:
{ width: 10, height: 30, ... }
Step 3: Process a Move Message
state.process(Message::Move(Point { x: 10, y: 15 }));What happens inside process():
messageisMessage::Move(Point { x: 10, y: 15 })- Second arm matches:
Message::Move(point) - Destructuring: the entire
Point { x: 10, y: 15 }is bound topoint - Calls
self.move_position(point) - State is now:
{ position: Point { x: 10, y: 15 }, ... }
Step 4: Process an Echo Message
state.process(Message::Echo(String::from("Hello world!")));What happens inside process():
messageisMessage::Echo(String::from("Hello world!"))- Third arm matches:
Message::Echo(string) - Destructuring: the
Stringis bound tostring - Calls
self.echo(string)— ownership of the String moves! - State is now:
{ message: "Hello world!", ... }
Step 5: Process a ChangeColor Message
state.process(Message::ChangeColor(255, 0, 255));What happens inside process():
messageisMessage::ChangeColor(255, 0, 255)- Fourth arm matches:
Message::ChangeColor(red, green, blue) - Destructuring:
red = 255,green = 0,blue = 255 - Calls
self.change_color(255, 0, 255) - State is now:
{ color: (255, 0, 255), ... }
Step 6: Process a Quit Message
state.process(Message::Quit);What happens inside process():
messageisMessage::Quit- Fifth arm matches:
Message::Quit - No destructuring needed (no data in this variant)
- Calls
self.quit() - State is now:
{ quit: true, ... }
Destructuring Patterns Summary
| Variant Definition | Match Pattern | What Gets Extracted |
|---|---|---|
Resize { width: u64, height: u64 } | Resize { width, height } | Named fields → variables |
Move(Point) | Move(point) | Single value → point |
Echo(String) | Echo(string) | Single value → string |
ChangeColor(u8, u8, u8) | ChangeColor(r, g, b) | Three values → three variables |
Quit | Quit | Nothing (unit variant) |
Key Takeaways
matchis exhaustive — you must handle every variant or use_as a catch-all- Destructuring happens in patterns — the left side of
=> - Variable names are up to you —
Message::Move(p)works just as well asMessage::Move(point) - Ownership transfers — when you match
Echo(string), the String is moved intostring - The compiler prevents mistakes — forget a variant? Compiler error!
🎯 This is one of Rust’s superpowers! Pattern matching + enums = safe, expressive code that handles all cases.