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:
| |
- The comma after an arm may be dropped if the
expris 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.
| |
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.
| |
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:
| |
? 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:
| |
if let
| |
An if let expression is shorthand for a match with just one pattern:
| |
Loops
| |
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.
| |
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.