Enums and Patterns
Enums
Term: The values that comprise enums are called variants or constructors.
As with structs, the compiler will implement features like ==
operator for you, but you have to ask. Also as with structs, enums can have methods within impl
blocks.
#![allow(unused)] fn main() { #[derive(Copy, Clone, Debug, PartialEq)] enum TimeUnit { Seconds, Minutes, Hours, Days, Months, Years, } impl TimeUnit { // Return the plural noun for this time unit fn plural(self) -> &'static str { match self { TimeUnit::Seconds => "seconds", TimeUnit::Minutes => "minutes", TimeUnit::Hours => "hours", TimeUnit::Days => "days", TimeUnit::Months => "months", TimeUnit::Years => "years", } } // Return the singular noun for this time unit fn singular(self) -> &'static str { self.plural().trim_right_matches('s') } } }
Enums with Data
Term: Enum constructors that take arguments that resemble tuples are called tuple variants. The constructors that take struct arguments are called struct variants. Constructors that take no arguments are called unit-like variants.
#![allow(unused)] fn main() { // Enum with tuple variants enum RoughTime { InThePast(TimeUnit, u32), JustNow, InTheFuture(TimeUnit, u32), } }
#![allow(unused)] fn main() { // Enum with struct variants enum Shape { Sphere { center: Point3d, radius: f32 }, Cuboid { corner1: Point3d, corner2: Point3d } } }
A single enum have can variants of all three kinds:
#![allow(unused)] fn main() { // Enum with unit-like, tuple, and struct variants enum RelationshipStatus { Single, InARelationShip, ItsComplicated(Option<String>), ItsExtremelyComplicated { car: DifferentialEquation, cdr: EarlyModernistPoem, } } }
All constructors and fields of a public enum are automatically public.
Enums in Memory
In memory, enums with data are stored as a small integer tag, plus enough memory to hold all of the fields of the largest variant. The tag tells Rust which constructor created the value, and therefore which fields it has.
Generic Enums
Enums can be generic, and generic data structures can be built with a few lines of code:
#![allow(unused)] fn main() { // An ordered collection of T's #[derive(Debug)] enum BinaryTree<T> { Empty, NonEmpty(Box<TreeNode<T>>), } // A node within the binary tree #[derive(Debug)] struct TreeNode<T> { element: T, left: BinaryTree<T>, right: BinaryTree<T>, } let tree = BinaryTree::NonEmpty(Box::new(TreeNode { element: "I'm a single-node tree", left: BinaryTree::Empty, right: BinaryTree::Empty, })); println!("{:?}", tree); }
Patterns
match
performs pattern matching. Think of it this way:
- Expressions produce values
- Patterns consume values
When a pattern contains identifiers, those become local variables in the code following the pattern.
Literals, Variables, and Wildcards in Patterns
Term: If you need a catch-all pattern, but don't care about the matched value, you can use a single underscore
_
as a pattern, called the wildcard pattern.
Rust requires that every single possible value is handled in a match
block. So even if you're certain that remaining cases can't occur, you at least add a fallback arm that panics:
Warning: Existing variables can't be used in patterns. This is because identifiers in patterns may only introduce new variables.
#![allow(unused)] fn main() { // This will fail because current_hex is an existing variable fn check_move(current_hex: Hex, click: Point) -> game::Result<Hex> { match point_to_hex(click) { None => Err("That's not a game space."), Some(current_hex) => Err("You're already there! Click somewhere else."), Some(other_hex) => Ok(other_hex), } } }
Tuple and Struct Patterns
Tuple patterns match tuples.
#![allow(unused)] fn main() { // Describe the location of a point on a Cartesian plane fn describe_point(x: i32, y: i32) -> &'static str { use std::cmp::Ordering::*; match (x.cmp(&0), y.cmp(&0)) { (Equal, Equal) => "at the origin", (_, Equal) => "on the x axis", (Equal, _) => "on the y axis", (Greater, Greater) => "in the first quadrant", (Less, Greater) => "in the second quadrant", _ => "somewhere else", } } }
Struct patterns match structs.
#![allow(unused)] fn main() { match balloon.location { Point { x: 0, y } => println!("straight up {} meters", height), Point { x, y } => println!("at ({}m, {}m)", x, y), } }
Reference Patterns
For very large struct type, it'd be too cumbersome to write out every single struct field in the pattern. Fortunately, you can use the ..
operator to mute the fields you don't care about:
#![allow(unused)] fn main() { match account { Account { name, language, .. } => { ui.greet(&name, &language); ui.show_settings(&account); // ERROR! use of moved value 'account' } } }
Keyword: The above code will fail because when we use
..
, the rest of theAccount
struct is dropped. So we need a pattern that borrow matched values instead of moving them. For that, we haveref
(ormut ref
, depending on context):#![allow(unused)] fn main() { match account { Account { ref name, ref language, .. } => { ui.greet(&ame, language); ui.show_settings(&account); // OK! } } }
Concept: The opposite of a
ref
pattern is a pattern that starts with&
. If a pattern starts with&
, that means that it matches a reference:#![allow(unused)] fn main() { match sphere.center() { &Point3d { x, y, z } => { ... } } }
You should remember that patterns and expressions are natural opposites:
- The expression
(x, y)
makes two values into a new tuple - The pattern
(x, y)
matches a tuple and breaks out the two values
The same principle applies to references:
- In an expression,
&
creates a reference - In a pattern,
&
matches a reference
Matching Multiple Possibilities
Operator: The vertical bar
|
can be used to combine several patterns in a singlematch
arm:#![allow(unused)] fn main() { let at_end = match chars.peek() { Some(&'\r') | Some(&'\n') | None => true, _ => false, }; }
You can also use ...
to match a whole range of values:
#![allow(unused)] fn main() { match next_char { '0' ... '9' => self.read_number(), 'a' ... 'z' | 'A' ... 'Z' => self.read_word(), ' ' | '\t' | '\n' | '\r' => self.skip_whitespace(), _ => self.handle_punctuation(), } }
Pattern Guards
Use the if
keyword to add a guard to a match arm. But, if a pattern moves any values, you can't put a guard on it.
#![allow(unused)] fn main() { match robot.last_known_location() { Some(ref point) if self.distance_to(point) < 10 => short_distance_strategy(point), Some(point) => long_distance_strategy(point), None => searching_strategy(), } }
@
Patterns
The x @ pattern
matches like like the given pattern
, but on success, instead of creating variables for parts of the matched value, it creates a single variable x
and moves or copies the whole value into it.
#![allow(unused)] fn main() { match self.get_selection() { rect @ Shape::Rect(..) => optimized_paint(&rect), other_shape => paint_outline(other_shape.get_outline()), } }
The @
pattern is also useful for ranges:
#![allow(unused)] fn main() { match chars.next() { Some(digit @ '0' ... '9') => read_number(digit, chars), ... } }
Where Patterns are Allowed
Come back to this.
Populating a Binary Tree
Finally we'll go back to the BinaryTree
enum written earlier and write an add
method for it that allows use to easily build a binary tree.
#![allow(unused)] fn main() { // An ordered collection of T's #[derive(Debug)] enum BinaryTree<T> { Empty, NonEmpty(Box<TreeNode<T>>), } // A node within the binary tree #[derive(Debug)] struct TreeNode<T> { element: T, left: BinaryTree<T>, right: BinaryTree<T>, } impl<T: Ord> BinaryTree<T> { fn add(&mut self, value: T) { // *self inside a match represents the existing tree match *self { BinaryTree::Empty => *self = BinaryTree::NonEmpty(Box::new(TreeNode { element: value, left: BinaryTree::Empty, right: BinaryTree::Empty, })), BinaryTree::NonEmpty(ref mut node) => if value <= node.element { node.left.add(value); } else { node.right.add(value); } } } } let mut tree = BinaryTree::Empty; for num in 0 .. 10 { tree.add(num); } println!("{:?}", tree); }