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 the Account struct is dropped. So we need a pattern that borrow matched values instead of moving them. For that, we have ref (or mut 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 single match 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);
}