Patterns

Rust patterns are a little like regular expressions for all your data. They’re used to test whether or not a value has a particular desired shape. They can extract several fields from a struct or tuple into local variables all at once. And like regular expressions, they are concise, typically doing it all in a single line of code.

Pattern matching an enum, struct, or tuple works as though Rust is doing a simple left-to-right scan, checking each component of the pattern to see if the value matches it.

Rust’s patterns are a mini-language of their own. A pattern can match a range of values. It can unpack tuples. It can match against individual fields of structs. It can chase references, borrow parts of a value, and more.

Patterns and expressions are natural opposites. Expressions produce values; patterns consume values. The two use a lot of the same syntax. The expression (x, y) makes two values into a new tuple, but the pattern (x, y) does the opposite: it matches a tuple and breaks out the two values. It’s the same with &. In an expression, & creates a reference. In a pattern, & matches a reference.

match Expressions

Patterns are most prominent in match expressions. The general form of a match expression is:

1
2
3
4
match value {
    pattern => expr,
    // ...
}
  • The comma after an arm may be dropped if the expr is a block.

Patterns are the parts that appear before the => symbol. The versatility of match stems from the variety of supported patterns that can be used to the left of => in each arm.

All arms of a match expression must have the same type.

In Rust, if and match can produce values.

Propagating Errors

Ordinary errors are handled using the Result type. Result is an enum.

1
2
3
4
enum Result<T, E> {
    Ok(T),
    Err(E),
}

The most thorough way of dealing with a Result is using a match expression.

In most places where we try something that could fail, we don’t want to catch and handle the error immediately. Instead, if an error occurs, we usually want to let our caller deal with it. We want errors to propagate up the call stack.

1
2
3
4
let weather = match get_weather(hometown) {
    Ok(success_value) => success_value,
    Err(err) => return Err(err)         // * return 
};

This kind of match statement is such a common pattern in Rust that the language provides the ? operator as shorthand for the whole thing. You can add a ? to any expression that produces a Result, such as the result of a function call:

1
let weather = get_weather(hometown)?;

? also works similarly with the Option type. In a function that returns Option, you can use ? to unwrap a value and return early in the case of None:

1
2
3
4
5
/* ok() returns the success value, if any, as an `Option<T>`. 
If `result` is a success result, this returns `Some(success_value)`; 
otherwise, it returns `None`, discarding the error value.
*/
let weather = get_weather(hometown).ok()?;

if let

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
if let pattern = expr {
    block1
} else {
    block2
}

// ...handle just one enum variant specially
if let RoughTime::InTheFuture(_, _) = user.date_of_birth() {
    user.set_time_traveler(true);
}

// ...run some code only if a table lookup succeeds
if let Some(document) = cache_map.get(&id) {
    return send_cached_response(document);
}   

An if let expression is shorthand for a match with just one pattern:

1
2
3
4
match expr {
    pattern => { block1 }
    _ => { block2 }
}

Loops

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
while let pattern = expr {
    block
}

// ...repeatedly try something until it succeeds
while let Err(err) = present_cheesy_anti_robot_task() {
    log_robot_attempt(err);
    // let the user try again (it might still be a human)
}

// ...manually loop over an iterator
while let Some(_) = lines.peek() {
    read_paragraph(&mut lines);
}


for pattern in iterable {
    block
}

for i in 0..20 {
    println!("{}", i);
} // prints 0 through 19

let strings: Vec<String> = error_messages();
for s in &strings {  // each String is moved into s here...
    println!("{}", s);
} 

Patterns in Other Places

Patterns are also allowed in several other places, typically in place of an identifier. The meaning is always the same: instead of just storing a value in a single variable, Rust uses pattern matching to take the value apart.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ...unpack a struct into three new local variables
let Track { album, track_number, title, .. } = song;

// ...unpack a function argument that's a tuple
fn distance_to((x, y): (f64, f64)) -> f64 { ... }

// ...iterate over keys and values of a HashMap
for (id, document) in &cache_map {
    println!("Document #{}: {}", id, document.title);
}

// ...automatically dereference an argument to a closure
// (handy because sometimes other code passes you a reference
// when you'd rather have a copy)
let sum = numbers.fold(0, |a, &num| a + num);

Each of these saves two or three lines of boilerplate code. The same concept exists in some other languages: in JavaScript, it’s called destructuring, while in Python, it’s unpacking.

In all four examples, we use patterns that are guaranteed to match. The pattern Point3d { x, y, z } matches every possible value of the Point3d struct type, (x, y) matches any (f64, f64) pair, and so on. Patterns that always match are special in Rust. They’re called irrefutable patterns, and they’re the only patterns allowed in the four places shown here (after let, in function arguments, after for, and in closure arguments).

A refutable pattern is one that might not match, like Ok(x), which doesn’t match an error result, or '0' ..= '9', which doesn’t match the character 'Q'. Refutable patterns can be used in match arms, because match is designed for them: if one pattern fails to match, it’s clear what happens next.

The four preceding examples are places in Rust programs where a pattern can be handy, but the language doesn’t allow for match failure.