Skip to content
📖 Auto vs RustBook contents

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 is keyword where Rust uses match. The pattern syntax is nearly identical, but the keyword is different: value is { ... } instead of match 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:

auto
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:

Matching on an optional value with `is`

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:

auto
let PATTERN = EXPRESSION
Using a pattern to destructure a tuple

Here, 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.

Mixing `if let`, `else if`, and `else if let`

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:

Using `while let` to process messages

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.

Using a pattern in a `for` loop to destructure a tuple

Function Parameters ​

Function parameters are also patterns. We can destructure tuples in function parameters:

A function with parameters that destructure a tuple

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:

Using `let else` with a refutable pattern

The let ... else form handles the case where the pattern doesn't match.

Pattern Syntax ​

Matching Literals ​

Matching literal values

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:

Variable shadowing in `is` arms

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 multiple patterns with `|`

Matching Ranges with ..= ​

Matching a range of values

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) ​

Destructuring a type

We can also destructure with literal values as part of the pattern:

Destructuring and matching literal values

This prints On the y axis at 7 because x is 0.

Enums ​

Destructuring enum variants

This prints Change color to red 0, green 160, and blue 255.

Nested Destructuring ​

Matching on nested enums

Ignoring Values in a Pattern ​

An Entire Value with _ ​

Using `_` to ignore a value

Parts of a Value with a Nested _ ​

Ignoring parts of a tuple

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 .. ​

Ignoring remaining fields 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:

Adding a match guard to a pattern

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:

Using a match guard to compare with an outer variable

@ 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:

Using `@` to bind while testing

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 ​

FeatureAuto (is)Rust (match)
Keywordismatch
Arm syntaxPATTERN -> EXPRPATTERN => EXPR,
ExhaustivenessRequiredRequired
Wildcard__
Multiple patterns1 | 21 | 2
Range1..=51..=5
Match guardif conditionif condition
Bindingx @ 1..=5x @ 1..=5
Ignore rest....
Destructure typeType(x, y)Type { x, y }
Destructure enumEnum.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.

Released under the MIT License.