2.22 Patterns

We’ve went over the basics of patterns in previous sections of this tome—as a remainder, patterns may be used to create sort of a simple explanations for Rust’s compiler to make use of when parsing out certain information.

One of Rust’s all different expressions using patterns are matches. A match matches a binding with all of its arms’ patterns to see if they correspond:

let age = 3;

match age
{
    1 => println!("You’re one year of age."), 
    2 => println!("You’re two years of age."), 
    3 => println!("You’re three years of age."), 
    _ => println!("You’re way too old for this program to handle.")
}

This program will print:

You’re three years of age.

The _ is not actually a default case as explained in an earlier section—it’s rather a pattern used to match any value whatsoever. It doesn’t matter what age is in this case, the _ pattern will always match it no matter what. One might say it completely ignores the binding matched against and completely disregards its value.

Multiple patterns

Unlike some languages—Rust allows us to match multiple patterns inside a match using |:

let age = 3;

match age
{
    1 | 2 | 3 => println!("You’re either one, two or three year(s) of age."), 
    _ => println!("You’re way too old for this program to handle.")
}

Both 1, 2 and 3 are patterns explaining unto the compiler what values we accept age being. The first arm of the match checks age with the three aforementioned patterns—should age be either of values one, two or three, the first arm is evaluated.

This will print:

You’re either one, two or three year(s) of age.

To clarify—this prints due to age being the value of three. The value of three is one of the patterns contained by the first arm—3.

Ranges

We can match a range of values using ...—these are mostly used with integers and chars, as most other values doesn’t have the ability to be matched using a range:

let age = 3;

match age
{
    1 ... 3 => println!("You’re either one, two or three year(s) of age."), 
    _ => println!("You’re way too old for this program to handle.")
}

This example looks very similar to our previous one inside Multiple patterns. The difference is that 1 ... 3 is a single pattern—in the previous example we were using three different patterns to do this identical match. The output in our console will be the same as that of our previous example.

Since chars can be symbolized as a range—it’s perfectly fine to make a range out of those too:

let c = 'a';

match c
{
    'a' ... 'z' | 'A' ... 'Z' => println!("Contained by the English alphabet."),
    _ => println!("Not contained by the English alphabet.")
}

This will print:

Contained by the English alphabet.

The two patterns—one for lowercase and one for uppercase characters—are required if we want the pattern to work for both uppercase and lowercase chars.

Destructuring

Different data types containing values can be destructured using patterns—to be correct, any compound data type can be destructured using patterns. Compound data types are types that hold multiple independent values. Let’s destructure the Coord struct from previous sections:

struct Coord
{
    x: i32, 
    y: i32
}

fn main()
{
    let coord = Coord { x: 12, y: 56 };

    // Destructure ‘coord’
    match coord
    {
        Coord { x, y } => println!("There’s a battleship at ({}, {}).", x, y)
    }
}

We use Coord { x, y } in order to destructure it. The coord will match that pattern because the pattern describes the struct perfectly—both by name and values! You can match any struct by typing in its exact name and exact names of its elements.

Different binding names

If we wanted to, we could give the bindings inside the pattern for the Coord new names—temporarily inside the match using :.

Coord { x: x_coord, y: y_coord } => println!("There’s a battleship at ({}, {}).", x_coord, y_coord)

We now can’t locate the values using plainly x and y—now we need to use xcoord and ycoord.

Exclude bindings

We can exclude one or more values using .. inside a pattern:

Coord { y, .. } => println!("The y coordinate for a battleship is {}.", y)

Any value you wish to keep, you write the name of—all other values when using .. will be discarded by the pattern.

Shadowing

Shadowing is a feature of programming languages that occur upon there being multiple variables—in Rust’s case bindings—with the same name within the same scope simultaneously. Let’s assume we’d want to match an i32 binding with another i32 binding:

let (x, y) = (2, 3);

match x
{
    y => println!("y is {}", y)
}

This will print 2, even though we can clearly see that we’re defining y to have the value of three in the let binding above. This happens due to match setting x to the new binding introduced inside the match—y. Inside the scope for the match, x and y have the same value—y is a valid blueprint for x.

Notice how we don’t need a *_* here? That’s because y corresponds to the blueprint for how x is made up. The x binding is a single element—the same goes for y. The match is correct and y is set to the value of x. If we—on the other hand—were to print out y after the match having taken place, it would have the value of three, since the y inside the scope for match goes out of scope and ultimately is destroyed at the end of matches code block. Our good old y isn’t touched at all by the match block.

Ignoring bindings

The _ inside patterns is actually used for ignoring certain bindings—rather than acting as though a default case. From the example on shadowing, you saw how y matched the blueprint for x. The _ nicely matches the blueprint for any variable—tis’ extra useful whenever you wish to check for Results returned by various different functions in Rust’s standard libarary:

use std::io;

fn main()
{
    let mut input = String::new();

    io::stdin().read_line(&mut input)
            .expect("Error reading line.");

    let num: i32 = match input.trim().parse()
    {
        Ok(num) => num, 
        Err(_) => 0
    };

    println!("{}", num);
}

In this example we don’t care about the information inside the Err part of the Result—so we put *_* inside of it, diregarding its type and value.

Tuples

We can use *_* when using a let to destructure a tuple:

let (_, y, z) = (32, 43, 12);

The y and z will contain the values of fourty-three and twelve respectively, but the value thirty-two is disregarded—for some reason we don’t seem to care about it.

This may be more useful when destructuring a tuple containing some information that otherwise would be moved rather than copied—considering the way borrowing works in Rust:

let t = (32, String::from("Hello, world!"), 12.2);
let (x, _, z) = t;

// Usually we wouldn’t have been able to use ‘t.1’, as the ‘String’ inside it
// would have been stolen by the destructuring. In this case it hasn’t—
// let's use it!
println!("{}", t.1);

// Couldn’t steal me, now could you?!

In the example above, the String inside t would have been stolen by the destructuring—this time around it’s not, due to our cleverness not allowing it to have the String at all!

Unbound

You could be silly about _ and use let bindings to bind a value unto nothing at all:

let _ = 342;

Although extremely silly—it works perfectly! The value of three hundred and fourty-two won’t be bound to anything—like such it will be discarded straight away upon being defined.

References

There are two keywords for retrieving a reference—alternatively mutable reference—to any variable used inside a pattern. Let’s add some code:

let name = String::from("Robert");

match name
{
    n => println!("{}", name)
}

In this example, Rust would complain that we’re attempting to use name after a move. The n steals name into itself. For this to work, we’re going to need to specify that we’d like it to take a reference of name instead:

let name = String::from("Robert");

match name
{
    ref n => println!("{}", name) // ref
}

This works perfectly fine! Perhaps we’d even like to be able to change name from inside our match:

let name = String::from("Robert");

match name
{
    ref mut n => println!("{}", name) // ref mut
}

This example isn’t going to work as name has been borrowed mutably—this means we need to wait for it to go out of scope before we can access name again. Rather than this, it might be clever to use n inside here instead:

let name = String::from("Robert");

match name
{
    ref mut n => println!("{}", n) // ref mut
}

And as if by magic—it works!

The keyword ref gives whatever binding coming after it a reference as a type. In this case its type would be &String—if our binding also has the keyword mut preceeding it, its binding would be of type &mut String.

Remember from Borrowing how we can use *binding_name to dereference it, so that we can in turn change its value—or values if it’s a struct.

Guards

You can use ifs inside matches after a pattern to check for further information regarding a binding:

struct Coord
{
    x: i32, 
    y: i32
}

fn main()
{
    let coord = Coord { x: 12, y: 56 };

    // Destructure ‘coord’
    match coord
    {
        Coord { x, .. } if x > 0 => println!("The battleship’s x is greater than zero."), 
        Coord { x, .. } if x < 0 => println!("The battleship’s x is less than zero."), 
        _ => println!("The battleship is centered in x.")
    }
}

We’re using an if to do further checks on the new values we retrieved from the dereferencing of coord. These are known as ‘match guards’ in Rust.

Guards can reach bindings from outside scope—as they’re not introducing new bindings. Let’s build on the previous example:

let battleship_exists = true;

// Destructure ‘coord’
match coord
{
    Coord { x, y } if battleship_exists => println!("There’s a battleship at ({}, {}).", x, y), 
    Coord { .. } if !battleship_exists => println!("There’s no battleship."), 
    _ => panic!("This is required, but can’t occur.")
}

This will print:

There’s a battleship at (12, 56).

This way we don’t need to bind up values should the battleship not exist.

Bindings

Sometimes tis’ desired to bind values inside a pattern to a new binding—this can be accomplished using @:

let age = 4;

match age
{
    n @ 1 ... 3 => println!("{} is—indeed—either one, two or three.", n), 
    n @ _ => println!("{} is a high number!", {});
}

Whatever value this matches, n will take on in both the cases. It’s not required to bind values in all arms of the match.

The above example will print:

4 is a high number!

The @ needs to be bound in every single pattern:

n @ 1 ... 3 | n @ 8 ... 12 => // ...

If it’s not bound in every single pattern present, we would have trouble accessing it should the pattern turn out as the one without a binding bound to the value—that is, if we’re using the value.

Exercises

  • There’s a struct which has the ability to store the length of a person’s beard, as well as that person’s beard’s color. Dereference the struct inside a match and print different messages depending on the person’s beard’s length using match guards, whilst only accessing the length of that person’s beard, rather than accessing both their beard’s length and their beard’s color.

Moreover

You’ve got the ability to combine these different parts of patterns anyway you’d like. There shouldn’t be any problem regarding it—Rust’s compiler is really great when it comes to pointing out errors in your code, whilst explaining why you’re receiving an error.

Drop