Last time we looked at Rust's data types. Once you have some data structure, you will want to get that data out. For structs, Rust has field access, just like C++. For tuples, tuple structs, and enums you must use destructuring (there are various convenience functions in the library, but they use destructuring internally). Destructuring of data structures doesn't happen in C++, but it might be familiar from languages such as Python or various functional languages. The idea is that just as you can create a data structure by filling out its fields with data from a bunch of local variables, you can fill out a bunch of local variables with data from a data structure. From this simple beginning, destructuring has become one of Rust's most powerful features. To put it another way, destructuring combines pattern matching with assignment into local variables.
Destructuring is done primarily through the let and match statements. The match statement is used when the structure being desctructured can have difference variants (such as an enum). A let expression pulls the variables out into the current scope, whereas match introduces a new scope. To compare:
The syntax for patterns (used after and before in the above example) in both cases is (pretty much) the same. You can also use these patterns in argument position in function declarations:
(Which is more useful for structs or tuple-structs than tuples).
Most initialisation expressions can appear in a destructuring pattern and they can be arbitrarily complex. That can include references and primitive literals as well as data structures. For example,
Note how we destructure through a reference by using in the patterns and how we use a mix of literals (, , ), wildcards (), and variables ().
You can use wherever a variable is expected if you want to ignore a single item in a pattern, so we could have used if we didn't care about the integer. In the first arm we destructure the embedded struct (a nested pattern) and in the second arm we bind the whole struct to a variable. You can also use to stand in for all fields of a tuple or struct. So if you wanted to do something for each enum variant but don't care about the content of the variants, you could write:
When destructuring structs, the fields don't need to be in order and you can use to elide the remaining fields. E.g.,
As a shorthand with structs you can use just the field name which creates a local variable with that name. The let statement in the above example created two new local variables and . Alternatively, you could write
Now we create local variables with the same names as the fields, in this case and .
There are a few more tricks to Rust's destructuring. Lets say you want a reference to a variable in a pattern. You can't use because that matches a reference, rather than creates one (and thus has the effect of dereferencing the object). For example,
Here, has type and is a copy of the field in .
To create a reference to something in a pattern, you use the keyword. For example,
Here, and both have type and are references to the fields in .
One last trick when destructuring is that if you are detructuring a complex object, you might want to name intermediate objects as well as individual fields. Going back to an earlier example, we had the pattern . In that pattern we named one field of the struct, but you might also want to name the whole struct object. You could write which would bind the struct object to , but then you would have to use field access for the fields, or if you wanted to only match with a specific value in a field you would have to use a nested match. That is not fun. Rust lets you name parts of a pattern using syntax. For example lets us name both a field (, for ) and the whole struct ().
That just about covers your options with Rust pattern matching. There are a few features I haven't covered, such as matching vectors, but hopefully you know how to use and and have seen some of the powerful things you can do. Next time I'll cover some of the subtle interactions between match and borrowing which tripped me up a fair bit when learning Rust.
2014-04-17: Updated for Rust v0.11-pre
Pattern matching is one of the features I like most about modern / functional style languages, also one I sincerely enjoy in Rust.
It works in a lot of different scenarios, the most basic is in a local scope using .
Should you have the need to capture a nested tuple or something, you can do that with the Haskell @ syntax:
You can destructure structs and rename the variables:
The order is not important:
and you can also ignore some variables:
you can match on ranges
match range and capture the value:
It also can be used to destructure struct variants:
You cannot just destructure an enum with multiple variants using :
You need to use a match instead of a simple let, because let can never fail using the second condition in match, the compiler knows, all possible paths have been exhausted.
One more cool feature of are guard clauses:
See the in the first line? This is called a guard, it will match only if the pattern matches and the guard clause is true.
Take also notice of the expression. As mentioned before all clauses need to be exhaustive. expands to .
allows to match on concrete values:
Remember, that a must contain all possibilities, otherwise you'll get an error, saying that you haven't covered all patterns. In this case if you'd leave out the last , the compiler would tell you: .
You can destructure vectors, too:
If you only care about the first or last values you can do this:
or if you want the first, last, but also the middle:
matching on a works just like matching any other vector
It works in a function's arguments:
You can also use destructuring in loops:
For comments head over to Reddit
blog comments powered by