Patterns and Matching
Patterns are a special syntax in Auto for matching against the structure of types, both complex and simple. Using patterns in conjunction with is expressions and other constructs gives you more control over a program's control flow. A pattern consists of some combination of the following:
- Literals
- Destructured arrays, enums, types, or tuples
- Variables
- Wildcards
- Placeholders
Some example patterns include x, (a, 3), and Some(Color.Red). In the contexts in which patterns are valid, these components describe the shape of data. Our program then matches values against the patterns to determine whether it has the correct shape of data to continue running a particular piece of code.
To use a pattern, we compare it to some value. If the pattern matches the value, we use the value parts in our code. Recall the is expressions in Chapter 6 that used patterns, such as the coin-sorting machine example. If the value fits the shape of the pattern, we can use the named pieces. If it doesn't, the code associated with the pattern won't run.
This chapter is a reference on all things related to patterns. We'll cover the valid places to use patterns, the difference between refutable and irrefutable patterns, and the different kinds of pattern syntax that you might see. By the end of the chapter, you'll know how to use patterns to express many concepts in a clear way.
Note: Auto uses the
iskeyword where Rust usesmatch. The pattern syntax is nearly identical, but the keyword is different:value is { ... }instead ofmatch value { ... }.
All the Places Patterns Can Be Used
Patterns pop up in a number of places in Auto, and you've been using them a lot without realizing it! This section discusses all the places where patterns are valid.
is Arms
As discussed in Chapter 6, we use patterns in the arms of is expressions. Formally, is expressions are defined as the keyword is, a value to match on, and one or more arms that consist of a pattern and an expression to run if the value matches that arm's pattern, like this:
value is {
PATTERN -> EXPRESSION
PATTERN -> EXPRESSION
PATTERN -> EXPRESSION
}For example, here's the is expression from Chapter 6 that matches on a ?int value in the variable x:
One requirement for is expressions is that they need to be exhaustive — all possibilities for the value must be accounted for. One way to ensure that you've covered every possibility is to have a catch-all pattern for the last arm: a variable name matching any value can never fail and thus covers every remaining case.
The particular pattern _ will match anything, but it never binds to a variable, so it's often used in the last arm.
let Statements
Every time you use a let statement, you've been using patterns. More formally, a let statement looks like this:
let PATTERN = EXPRESSIONHere, we match a tuple against a pattern. Auto compares the value (1, 2, 3) to the pattern (x, y, z) and sees that the value matches the pattern — that is, it sees that the number of elements is the same in both — so Auto binds 1 to x, 2 to y, and 3 to z.
If the number of elements in the pattern doesn't match the number of elements in the tuple, the overall type won't match and we'll get a compiler error.
Conditional if let Expressions
In Chapter 6, we discussed how to use if let expressions mainly as a shorter way to write the equivalent of an is that only matches one case.
Auto uses the same if let syntax as Rust. The if let can also introduce new variables that shadow existing variables, just like Rust.
while let Conditional Loops
Similar to if let, the while let conditional loop allows a while loop to run for as long as a pattern continues to match:
This example prints 1, 2, and then 3.
for Loops
In a for loop, the value that directly follows the keyword for is a pattern. Listing 19-5 demonstrates how to use a pattern in a for loop to destructure a tuple.
Function Parameters
Function parameters are also patterns. We can destructure tuples in function parameters:
This code prints Current location: (3, 5). The values &(3, 5) match the pattern &(x, y), so x is 3 and y is 5.
Refutability: Whether a Pattern Might Fail to Match
Patterns come in two forms: refutable and irrefutable. Patterns that will match for any possible value passed are irrefutable. An example would be x in the statement let x = 5 because x matches anything and therefore cannot fail to match. Patterns that can fail to match for some possible value are refutable. An example would be Some(x) in the expression if let Some(x) = a_value because if the value is None, the Some(x) pattern will not match.
Function parameters, let statements, and for loops can only accept irrefutable patterns because the program cannot do anything meaningful when values don't match. The if let and while let expressions accept refutable and irrefutable patterns.
If we try to use a refutable pattern where an irrefutable pattern is required, the compiler will produce an error:
The let ... else form handles the case where the pattern doesn't match.
Pattern Syntax
Matching Literals
This code prints one because the value in x is 1.
Matching Named Variables
Named variables are irrefutable patterns that match any value. However, there is a complication when you use named variables in is expressions. Because is starts a new scope, variables declared as part of a pattern inside these expressions will shadow those with the same name outside:
This prints Matched, y = 5 because the Some(y) pattern introduces a new y that shadows the outer y = 10. At the end, it prints at the end: x = Some(5), y = 10 because the inner y's scope has ended.
Matching Multiple Patterns with |
Matching Ranges with ..=
If x is 1, 2, 3, 4, or 5, the first arm will match. Ranges are only allowed with numeric or char values.
Destructuring to Break Apart Values
Types (Structs)
We can also destructure with literal values as part of the pattern:
This prints On the y axis at 7 because x is 0.
Enums
This prints Change color to red 0, green 160, and blue 255.
Nested Destructuring
Ignoring Values in a Pattern
An Entire Value with _
Parts of a Value with a Nested _
This prints Some numbers: 2, 8, 32.
An Unused Variable Starting with _
If you create a variable but don't use it, Auto will issue a warning. You can suppress the warning by starting the name with an underscore: _x still binds the value, but _ does not bind at all. This distinction matters for ownership: _ won't move the value, but _x will.
Remaining Parts with ..
The .. pattern ignores any parts of a value that we haven't explicitly matched. It must be unambiguous — you can only use .. once per pattern.
Match Guards
A match guard is an additional if condition, specified after the pattern in an is arm, that must also match for that arm to be chosen:
This prints The number 4 is even.
Match guards are useful for expressing more complex ideas than a pattern alone allows. You can also use the guard to compare against outer variables:
@ Bindings
The @ operator lets us create a variable that holds a value at the same time we're testing that value for a pattern match:
This prints Found an id in range: 5. By specifying id @ before the range 3..=7, we capture whatever value matched the range while also testing it.
is vs match Quick Reference
| Feature | Auto (is) | Rust (match) |
|---|---|---|
| Keyword | is | match |
| Arm syntax | PATTERN -> EXPR | PATTERN => EXPR, |
| Exhaustiveness | Required | Required |
| Wildcard | _ | _ |
| Multiple patterns | 1 | 2 | 1 | 2 |
| Range | 1..=5 | 1..=5 |
| Match guard | if condition | if condition |
| Binding | x @ 1..=5 | x @ 1..=5 |
| Ignore rest | .. | .. |
| Destructure type | Type(x, y) | Type { x, y } |
| Destructure enum | Enum.Variant(x) | Enum::Variant(x) |
Summary
Auto's patterns are very useful in distinguishing between different kinds of data. When used in is expressions, Auto ensures that your patterns cover every possible value, or your program won't compile. Patterns in let statements and function parameters make those constructs more useful, enabling the destructuring of values into smaller parts and assigning those parts to variables. We can create simple or complex patterns to suit our needs.
The key syntactic difference from Rust is the use of is instead of match, and -> instead of =>. The pattern syntax itself — literals, variables, wildcards, ranges, guards, and @ bindings — is nearly identical.
In the next chapter, we'll look at some advanced aspects of Auto's features, including sys (Auto's equivalent of unsafe) and comptime metaprogramming.