Step 6 of 7
Methods on enums and matches!
Enums get impl blocks just like structs, and the method body is usually a match self. Inside the impl, Self::Circle is shorthand for Shape::Circle.
enum Shape {
Circle(f64),
Rectangle { width: f64, height: f64 },
}
impl Shape {
fn area(&self) -> f64 {
match self {
Self::Circle(r) => std::f64::consts::PI * r * r,
Self::Rectangle { width, height } => width * height,
}
}
fn scaled(&self, factor: f64) -> Shape {
match self {
Self::Circle(r) => Self::Circle(r * factor),
Self::Rectangle { width, height } => Self::Rectangle {
width: width * factor,
height: height * factor,
},
}
}
fn is_round(&self) -> bool {
matches!(self, Self::Circle(_))
}
}
fn main() {
let shapes = [Shape::Circle(1.0), Shape::Rectangle { width: 2.0, height: 3.0 }];
for s in &shapes {
let big = s.scaled(2.0);
println!("area {:.2}, doubled {:.2}, round: {}", s.area(), big.area(), s.is_round());
}
}
area 3.14, doubled 12.57, round: true
area 6.00, doubled 24.00, round: false
match selfwith&selfworks exactly like matching a&Shape: the bindings (r,width) are references, and arithmetic works through them.scaledbuilds a new value of the same variant from the old one's data.Self::Circle(_)matches any circle, ignoring its radius:_inside a pattern means "something, I don't care what".
matches!
matches!(value, pattern) is true if the value fits the pattern, false otherwise. It's a short form of a match with a => true arm and a _ => false arm, and it's ideal for "is it this kind?" questions, which come up constantly in if conditions:
if matches!(light, Light::Red) { /* stop */ }
Small enums: derive Copy
An enum whose variants hold no data (or only Copy data) can derive Clone and Copy. Then methods can take self by value and the original stays usable, which suits state machines: light = light.next();.
#[derive(Debug, Clone, Copy)]
enum Light {
Red,
Green,
Yellow,
}
impl Light {
fn next(self) -> Self {
match self {
Self::Red => Self::Green,
Self::Green => Self::Yellow,
Self::Yellow => Self::Red,
}
}
fn can_go(self) -> bool {
matches!(self, Self::Green)
}
}
fn main() {
let mut light = Light::Red;
for _ in 0..3 {
light = light.next();
println!("{:?}: go = {}", light, light.can_go());
}
}
Green: go = true
Yellow: go = false
Red: go = false
Associated functions work on enums too. A common one turns outside input into a variant, returning an Option because the input may not name any variant: fn from_char(c: char) -> Option<Self>. You'll write one in the last challenge of this step.
Your task
A game map is made of cells. Write three methods on Cell:
fn is_walkable(&self) -> bool: everything except a wall. Usematches!.fn symbol(&self) -> char:'.'for empty,'#'for a wall,'#39;for a coin.fn value(&self) -> u32: a coin's value, 0 for every other cell.
Challenge 2
Give Direction some methods. It derives Copy, so the methods take self by value:
fn turn_right(self) -> Self: North → East → South → West → North.fn turn_left(self) -> Self: the other way round.fn opposite(self) -> Self: hint, that's two right turns.fn is_vertical(self) -> bool: North or South. Usematches!(one for each, joined with||).
Challenge 3
Now control a robot with a string of letters. Write:
Direction::from_char(c: char) -> Option<Direction>:'N','E','S','W'give the direction; any other character givesNone.fn follow(&mut self, path: &str) -> boolonRobot: for each character ofpath, move one step (North is y + 1, East is x + 1, South is y - 1, West is x - 1). If a character isn't a direction, stop right there and returnfalse; returntrueif the whole path was followed.
for c in path.chars() walks the characters, and let...else handles the bad ones neatly.
Previous: if let and let...else Next: Modeling with enums: parsing commands