mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 04:54:17 -06:00
686 lines
25 KiB
Markdown
686 lines
25 KiB
Markdown
# Pattern Syntax
|
|
Here we gather all the syntax valid in patterns and discuss why and when you might want to use each one.
|
|
|
|
## Matching Literals
|
|
As you saw previously in Ch6, you can match patterns against literals directly.
|
|
|
|
Here is an example of this
|
|
```rust
|
|
let x = 1;
|
|
|
|
match x {
|
|
1 => println!("one"),
|
|
2 => println!("two"),
|
|
3 => println!("three"),
|
|
_ => println!("anything"),
|
|
}
|
|
```
|
|
The code prints `one` because the value in `x` is 1.
|
|
|
|
This syntax is useful when you want your code to take an action if it gets a particular concrete value.
|
|
|
|
## Matching Named Variables
|
|
Named variables are irrefutable patterns that match any value, and we have used them many times.
|
|
|
|
However, there is a complication when you use named variables in `match`, `if let`, or `while let` expressions.
|
|
|
|
Because each kinds of expression starts a new scope, variables declared as part of a pattern inside the expression will shadow those with the same name outside, as is the case with all variables.
|
|
|
|
Here we declare a variable named `x` with the value `Some(5)` and a variable `y` with the value `10`.
|
|
|
|
|
|
Next we create a `match` expression on the value `x`.
|
|
|
|
Look at the patterns in the match arms and `println!` at the end, and try to figure out what the code will print before running this code or reading further.
|
|
```rust
|
|
let x = Some(5);
|
|
let y = 10;
|
|
|
|
match x {
|
|
Some(50) => println!("Got 50"),
|
|
Some(y) => println!("Matched, y = {y}"),
|
|
_ => println!("Default case, x = {x:?}"),
|
|
}
|
|
|
|
println!("at the end: x = {x:?}, y = {y}");
|
|
```
|
|
Lets run through what happens when the `match` expression runs.
|
|
|
|
The pattern in the first match arm doesn't match the defined value of `x`, so the code continues.
|
|
|
|
The pattern in the second match arm introduces a new variable named `y` that will match any value inside a `Some` value.
|
|
|
|
Because we are in a new scope inside the `match` expression, this is a new `y` variable, not the `y` we declared at the beginning with the value 10.
|
|
|
|
This new `y` binding will match any value inside a `Some`, which is what we have in `x`.
|
|
|
|
Therefore the new `y` binds to the inner value of the `Some` in `x`.
|
|
|
|
That value is `5` so the expression for that arm executes and prints `Matched , y = 5`.
|
|
|
|
If `x` has been a `None` value instead of `Some(5)`, the patterns in the first two arms wouldn't have matched. so the value would have matched to the underscore.
|
|
|
|
We didn't introduce the `x` variable in the pattern of the underscore arm, so the `x` in the expression is still the outer `x` that hasn't been shadowed.
|
|
|
|
For this hypothetical case, the `maatch` would pint `Default case, x = None`.
|
|
|
|
When the `match` expression is done, its scope ends, and so does the scope of the inner `y`.
|
|
|
|
The last `println!` produces `at the end: x = Some(5), y = 10`.
|
|
|
|
To create a `match` expression that compares the values of the outer `x` and `y` rather than introducing a new variable which shadows the exiting `y` variable.
|
|
|
|
We would need to use a match guard conditional instead.
|
|
|
|
This will be covered later in the ch.
|
|
|
|
## Multiple Patterns
|
|
You can match multiple patterns using the `|` syntax, which is the pattern *or* operator.
|
|
|
|
For example in the following code we match the value of `x` against the match arms, the first of which has an *or* option, meaning if the value of `x` matches either of the values in that arm, that arm's code will run
|
|
```rust
|
|
let x = 1;
|
|
|
|
match x {
|
|
1 | 2 => println!("one or two"),
|
|
3 => println!("three"),
|
|
_ => println!("anything"),
|
|
}
|
|
```
|
|
This code prints `one or two`.
|
|
|
|
## Matching Ranges of Values with `..=`
|
|
The `..=` syntax allows us to match to an inclusive range of values.
|
|
|
|
Here when a pattern matches any of the values within the given range, that arm will execute
|
|
```rust
|
|
let x = 5;
|
|
|
|
match x {
|
|
1..=5 => println!("one through five"),
|
|
_ => println!("something else"),
|
|
}
|
|
```
|
|
If `x` is 1, 2, 3, 4, or 5, the first arm will match.
|
|
|
|
This syntax is more convenient for multiple match values that using the `|` operator to express the same idea.
|
|
|
|
If we were to use `|` we would need to do `1 | 2 | 3 | 4 | 5`.
|
|
|
|
Specifying a range is much shorter, especially if we want to match, say any number between 1 and 1,000.
|
|
|
|
The compiler checks that the range isn't empty at compile time, because only types for which Rust can tell if a range is empty or not are `char` and numeric values, ranges are only allowed with numeric or `car` values.
|
|
|
|
Rust can tell that `'c'` is within the first pattern's range and prints `early ASCII letter`.
|
|
|
|
## Destructuring to Break Apart Values
|
|
We can also use patterns to destructure structs, enums, and tuples to use different parts of these values.
|
|
|
|
We will walk through each value.
|
|
|
|
### Destructuring Structs
|
|
This code shows a `Point` struct with two fields, `x` and `y`, that we can break apart using a patter with a `let` statement
|
|
```rust
|
|
struct Point {
|
|
x: i32,
|
|
y: i32,
|
|
}
|
|
|
|
fn main() {
|
|
let p = Point { x: 0, y: 7 };
|
|
|
|
let Point { x: a, y: b } = p;
|
|
assert_eq!(0, a);
|
|
assert_eq!(7, b);
|
|
}
|
|
```
|
|
This creates the variables `a` and `b` that match the values of the `x` and `y` fields of the `p` struct.
|
|
|
|
This shows that the names of the variables in the pattern don't have to match the field names of the struct.
|
|
|
|
However it is common to match the variable names to the field names to make it easier to remember which variables came from which fields.
|
|
|
|
Because of this common usage, and because writing `let Point { x: x, y: y} = p;` contains a lot of duplication, Rust has a shorthand for patterns that match struct fields.
|
|
|
|
You only need to list the name of the struct field, and the variables created from the pattern will have the same names.
|
|
|
|
|
|
This code behaves in the same way as om the code before, but the variables created in the `let` pattern are `x` and `y` instead of `a` and `b`.
|
|
```rust
|
|
struct Point {
|
|
x: i32,
|
|
y: i32,
|
|
}
|
|
|
|
fn main() {
|
|
let p = Point { x: 0, y: 7 };
|
|
|
|
let Point { x, y } = p;
|
|
assert_eq!(0, x);
|
|
assert_eq!(7, y);
|
|
}
|
|
```
|
|
This code creates the variables `x` and `y` that match the `x` and `y` fields of the `p` variable.
|
|
|
|
The outcome is that the variables `x` and `y` contain the values form the `p` struct.
|
|
|
|
We can also destructure with literal values as part of the struct pattern rather than creating variables for all the fields.
|
|
|
|
Doing this allows us to test some of the fields for particular values while creating variables to destructure the other fields.
|
|
|
|
Here we have a `match` expression that separates `Point` values into three cases.
|
|
|
|
Points that lie directly on the `x` axis (which is true when `y = 0`)
|
|
|
|
On the `y` axis (`x = 0`)
|
|
|
|
Or Neither
|
|
```rust
|
|
fn main() {
|
|
let p = Point { x: 0, y: 7 };
|
|
|
|
match p {
|
|
Point { x, y: 0 } => println!("On the x axis at {x}"),
|
|
Point { x: 0, y } => println!("On the y axis at {y}"),
|
|
Point { x, y } => {
|
|
println!("On neither axis: ({x}, {y})");
|
|
}
|
|
}
|
|
}
|
|
```
|
|
The first arm will match any point that lies on the `x` axis by specifying that the `y` field matches if its value matches the literal `0`.
|
|
|
|
The pattern still creates an `x` variable that we can use in the code for this arm.
|
|
|
|
The second arm matches any point on the `y` axis by specifying that the `x` field matches if its value is `0` and creates a variable `y` for the value of the `y` field.
|
|
|
|
The third arm doesn't specify any literals, so it matches any other `Point` and creates variables that we can use in the code for this arm.
|
|
|
|
Here the value `p` matches the second arm by virtue of `x` containing a 0, so this code will print `On the x axis at 0`.
|
|
|
|
### Destructuring Enums
|
|
We have destructured enums before, but haven't explicitly discussed that the pattern to destructure an enum corresponds to the way the data stored within the enum is defined.
|
|
|
|
For example, here we use the `Message` enum from Ch6 and write a `match` with patterns that will destructure each inner value.
|
|
```rust
|
|
enum Message {
|
|
Quit,
|
|
Move { x: i32, y: i32 },
|
|
Write(String),
|
|
ChangeColor(i32, i32, i32),
|
|
}
|
|
|
|
fn main() {
|
|
let msg = Message::ChangeColor(0, 160, 255);
|
|
|
|
match msg {
|
|
Message::Quit => {
|
|
println!("The Quit variant has no data to destructure.");
|
|
}
|
|
Message::Move { x, y } => {
|
|
println!("Move in the x direction {x} and in the y direction {y}");
|
|
}
|
|
Message::Write(text) => {
|
|
println!("Text message: {text}");
|
|
}
|
|
Message::ChangeColor(r, g, b) => {
|
|
println!("Change the color to red {r}, green {g}, and blue {b}");
|
|
}
|
|
}
|
|
}
|
|
```
|
|
This code will print `Change the color to red 0, green 160, and blue 255`.
|
|
|
|
Try changing the value of `msg` to see the code form the other arms run.
|
|
|
|
For enum variants without any data, like `Message::Quit`.
|
|
|
|
We can't destructure the value any further.
|
|
|
|
We can only match on the literal `Message::Quit` value and no variables are in that pattern.
|
|
|
|
For struct-like enum variants, like `Message::Move`.
|
|
|
|
We can use a pattern similar to the pattern we specify to match structs.
|
|
|
|
After the variant name, we place curly brackets and then list the fields with variable so we break apart the pieces to use in the code for this arm.
|
|
|
|
We did use the shorthand form as we did before.
|
|
|
|
For tuple-like enum variants, like `Message::Write` that holds a tuple with one element and `Message::ChangeColor` that holds a tuple with three elements.
|
|
|
|
The pattern is similar to the pattern we specify to match tuples.
|
|
|
|
The number of variables in the pattern must match the number of elements in the variant we are matching.
|
|
|
|
### Destructuring Nested Structs and Enums
|
|
So we have seen examples that have all been matching structs or enums on level deep.
|
|
|
|
Matching can work on nested items too.
|
|
|
|
For example, we can refactor the code form before to support RGB and HSV colors in the `ChangeColor` message.
|
|
```rust
|
|
enum Color {
|
|
Rgb(i32, i32, i32),
|
|
Hsv(i32, i32, i32),
|
|
}
|
|
|
|
enum Message {
|
|
Quit,
|
|
Move { x: i32, y: i32 },
|
|
Write(String),
|
|
ChangeColor(Color),
|
|
}
|
|
|
|
fn main() {
|
|
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
|
|
|
|
match msg {
|
|
Message::ChangeColor(Color::Rgb(r, g, b)) => {
|
|
println!("Change color to red {r}, green {g}, and blue {b}");
|
|
}
|
|
Message::ChangeColor(Color::Hsv(h, s, v)) => {
|
|
println!("Change color to hue {h}, saturation {s}, value {v}");
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
```
|
|
The first arm in the `match` expression, matches a `Message::ChangeColor` enum variant that contains a `Color::Rgb` variant.
|
|
|
|
Then the pattern binds to the three inner `i32` values.
|
|
|
|
The second arm also matches a `Message::ChangeColor` enum variant, but the inner enum matches `Color::Hsv` instead.
|
|
|
|
We can specify these complex conditions in one `match` expression, even though two enums are involved.
|
|
|
|
### Destructuring Structs and Tuples
|
|
We can also mix, match and nest destructuring patterns in even more complex ways.
|
|
|
|
This example shows a complicated destructure where we nest structs and tuples inside a tuple and destructure all the primitive values out
|
|
```rust
|
|
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
|
|
```
|
|
Here the code lets us break complex types into their component parts so we can use the values we are interested in separately.
|
|
|
|
Destructuring with patters is a convenient way to use pieces of values such as the value from each field in a struct, separately from each other.
|
|
|
|
## Ignoring Values in a Pattern
|
|
Sometimes it is useful to ignore values in a pattern, such as in the last arm of a `match`, to get a catchall that doesn't actually do anything but does account for all remaining possible values.
|
|
|
|
There are a few ways to ignore entire values or pats of values in a pattern:
|
|
- Using the `_` pattern
|
|
- Using the `_` pattern within another pattern
|
|
- Using a name that starts with an underscore
|
|
- Using `..` to ignore remaining parts of a value.
|
|
### Ignoring an Entire Value with `_`
|
|
We have used the underscore as a wildcard pattern that will match any value but not bind to the value.
|
|
|
|
This is particularly useful as the last arm in a `match` expression.
|
|
|
|
We can also use it in any pattern, including function parameters
|
|
```rust
|
|
fn foo(_: i32, y: i32) {
|
|
println!("This code only uses the y parameter: {y}");
|
|
}
|
|
|
|
fn main() {
|
|
foo(3, 4);
|
|
}
|
|
```
|
|
This will completely ignore the value `3` that is passed as the first argument.
|
|
|
|
You will print `This code only uses the y parameter: 4`.
|
|
|
|
In most cases when you no longer need a particular function parameter, you would change the signature so it doesn't include the used parameter.
|
|
|
|
Ignoring a function parameter can be especially useful in cases when you are implementing a trait when you need a certain type signature but the function body in your implementation doesn't need one of the parameters.
|
|
|
|
Thus then you avoid getting a compiler warning about unused function parameters, as you would if you used a name instead.
|
|
|
|
### Ignoring Parts of a Value with a Nested `_`
|
|
You can also use `_` inside another pattern to ignore just part of a value.
|
|
|
|
Lets say when you want to test for only part of a value but have no use for the other parts in the corresponding code we want to run.
|
|
|
|
Here shows code responsible for managing a setting's value.
|
|
|
|
The business requirements are that the user should not be allowed to overwrite an existing customization of setting but can unset the setting and give it a value if it is currently unset.
|
|
```rust
|
|
let mut setting_value = Some(5);
|
|
let new_setting_value = Some(10);
|
|
|
|
match (setting_value, new_setting_value) {
|
|
(Some(_), Some(_)) => {
|
|
println!("Can't overwrite an existing customized value");
|
|
}
|
|
_ => {
|
|
setting_value = new_setting_value;
|
|
}
|
|
}
|
|
|
|
println!("setting is {setting_value:?}");
|
|
```
|
|
|
|
This will print `Can't overwrite an existing customized value` and then `setting is Some(5)`.
|
|
|
|
In the first arm, we don't need to match on or use the values inside either `Some` variant, but we do need to test for the case when `setting_value` and `new_setting_value` are the `Some` variant.
|
|
|
|
In this case, we print the reason for not changing `setting_value`, and it doesn't get changed.
|
|
|
|
In all other cases (if either `setting_value` or `new_setting_value` are `None`) expressed by the `_` pattern in the second arm.
|
|
|
|
We want to allow `new_setting_value` to become `setting_value`.
|
|
|
|
We could also use underscores in multiple places within one pattern to ignore particular vlaues.
|
|
|
|
Here shows an example of ignoring the second and fourth values in a tuple of five items.
|
|
```rust
|
|
let numbers = (2, 4, 8, 16, 32);
|
|
|
|
match numbers {
|
|
(first, _, third, _, fifth) => {
|
|
println!("Some numbers: {first}, {third}, {fifth}")
|
|
}
|
|
}
|
|
```
|
|
This will print `Some numbers: 2, 8, 32`, and the values 4 and 16 will be ignored.
|
|
|
|
### Ignoring an Unused Variable by Starting Its Name with `_`
|
|
If you create a variable but don't use it anywhere, Rust will usually issue a warning because an unused variable could be a bug.
|
|
|
|
However sometimes it is useful to be able to create a variable you will not use yet.
|
|
|
|
Such as when you are prototyping or just starting a project.
|
|
|
|
In this situation, you can tell Rust not to warn you about the unused variable by starting by starting the name of the variable with an underscore.
|
|
|
|
Here we create two unused variables, but when we compile, we should only get warning about one of them.
|
|
```rust
|
|
fn main() {
|
|
let _x = 5;
|
|
let y = 10;
|
|
}
|
|
```
|
|
We get a warning about not using the variable `y`, but we don't get a warning about not using `_x`.
|
|
|
|
The only difference between using only `_` and using a name that starts with an underscore.
|
|
|
|
The syntax `_x` still binds the value to the variable, whereas `_` doesn't bind at all.
|
|
|
|
To show a case where this distinction matters, this will provide us with an error.
|
|
```rust
|
|
let s = Some(String::from("Hello!"));
|
|
|
|
if let Some(_s) = s {
|
|
println!("found a string");
|
|
}
|
|
|
|
println!("{s:?}");
|
|
```
|
|
We will receive an error because the `s` value will still be moved into `_s`, which prevents us from using `s` again.
|
|
|
|
Using the underscore by itself doesn't ever bind to the value.
|
|
|
|
This code will compile without an errors because `s` doesn't get moved into `_`.
|
|
```rust
|
|
let s = Some(String::from("Hello!"));
|
|
|
|
if let Some(_) = s {
|
|
println!("found a string");
|
|
}
|
|
|
|
println!("{s:?}");
|
|
```
|
|
This code works just fine because we never bind `s` to anything; it isn't moved.
|
|
|
|
### Ignoring Remaining Parts of a Value with `..`
|
|
Values that have many parts, we can use the `..` syntax to use specific parts and ignore the rest, avoiding the need to list underscores for each ignored value.
|
|
|
|
The `..` pattern ignores any parts of a value that we haven't explicitly matched in the rest of the pattern.
|
|
|
|
In this code we have a `Point` struct that holds a coordinate in three-dimensional space.
|
|
|
|
In this `match` expression, we want to operate only on the `x` coordinate and ignore the values in the `y` and `z` fields.
|
|
```rust
|
|
struct Point {
|
|
x: i32,
|
|
y: i32,
|
|
z: i32,
|
|
}
|
|
|
|
let origin = Point { x: 0, y: 0, z: 0 };
|
|
|
|
match origin {
|
|
Point { x, .. } => println!("x is {x}"),
|
|
}
|
|
```
|
|
Here we list the `x` value and then just include the `..` pattern.
|
|
|
|
This is far quicker than having to list `y: _` and `z: _`, particularly when we are working with structs that have lots of fields in situations where only one or two fields are relevant.
|
|
|
|
The syntax `..` will expand to as many values as it needs to be.
|
|
|
|
This is an example of how to use `..` with a tuple.
|
|
```rust
|
|
fn main() {
|
|
let numbers = (2, 4, 8, 16, 32);
|
|
|
|
match numbers {
|
|
(first, .., last) => {
|
|
println!("Some numbers: {first}, {last}");
|
|
}
|
|
}
|
|
}
|
|
```
|
|
Output
|
|
```
|
|
Some numbers: 2, 32
|
|
```
|
|
The fist and last value are matched with `first` and `last`.
|
|
|
|
The `..` will match and ignore everything in the middle.
|
|
|
|
Using `..` must be unambiguous.
|
|
|
|
If it unclear which values are intended for matching and which should be ignored, Rust will give us an error.
|
|
|
|
Here shows an example of using `..` ambiguously, so it will not compile.
|
|
```rust
|
|
fn main() {
|
|
let numbers = (2, 4, 8, 16, 32);
|
|
|
|
match numbers {
|
|
(.., second, ..) => {
|
|
println!("Some numbers: {second}")
|
|
},
|
|
}
|
|
}
|
|
```
|
|
We get this compiler error
|
|
```
|
|
$ cargo run
|
|
Compiling patterns v0.1.0 (file:///projects/patterns)
|
|
error: `..` can only be used once per tuple pattern
|
|
--> src/main.rs:5:22
|
|
|
|
|
5 | (.., second, ..) => {
|
|
| -- ^^ can only be used once per tuple pattern
|
|
| |
|
|
| previously used here
|
|
|
|
error: could not compile `patterns` (bin "patterns") due to 1 previous error
|
|
```
|
|
It is impossible for Rust to determine how many values in the tuple to ignore before matching a value with `second` and then how many further values to ignore after.
|
|
|
|
This code could mean we want to ignore `2`, bind `second` to `4` and then ignore `8` and `16` and `32`.
|
|
|
|
Or it could mean we want to ignore `2` and `4`, bind `second` to `8`, and then ignore `16` and `32`.
|
|
|
|
Or any other case.
|
|
|
|
The variable name `second` doesn't mean anything special to Rust, we get a compiler error because using `..` in two places like this ambiguous.
|
|
|
|
## Extra Conditionals with Match Guards
|
|
A *match guard* is an additional `if` condition, specified after the pattern in a `match` arm, that must also match for that arm to be chosen.
|
|
|
|
Match guards are useful for expressing complex ideas than a pattern alone allows.
|
|
|
|
They are only available in `match` expressions, not in `if let` or `while let` expressions.
|
|
|
|
The condition can use variables created in the pattern.
|
|
|
|
Here shows a `match` where the first arm has the pattern `Some(x)` and also has a match guard of `if x % 2 == 0`
|
|
|
|
This will be true if the number is even.
|
|
```rust
|
|
let num = Some(4);
|
|
|
|
match num {
|
|
Some(x) if x % 2 == 0 => println!("The number {x} is even"),
|
|
Some(x) => println!("The number {x} is odd"),
|
|
None => (),
|
|
}
|
|
```
|
|
This will print `The number 4 is even`.
|
|
|
|
When `num` is compared to the pattern in the first arm, it matches.
|
|
|
|
This is because `Some(4)` matches `Some(x)`.
|
|
|
|
Next the match guard checks whether the remainder of dividing `x` by 2 is equal to 0.
|
|
|
|
Because this is true the first arm is selected.
|
|
|
|
If `num` had been `Some(5)` instead, the match guard in the first arm would have been false.
|
|
|
|
This is because the remainder of 5 / 2 is 1, which is not equal to 0.
|
|
|
|
Rust would then go to the second arm, which doesn't have a match guard and therefore matches any `Some` variant.
|
|
|
|
There is not may to express the `if x % 2 == 0` condition within a pattern, so the match guard gives us the ability to express this logic.
|
|
|
|
The downside of this is that the compiler doesn't try to check for exhaustiveness when match guard expressions are involved.
|
|
|
|
Before we mentioned that we could use match guards to solve our pattern shadowing problem.
|
|
|
|
Recall that after we created a new variable inside the pattern in the `match` expression instead of using the variable outside the `match`.
|
|
|
|
This new variable meant we couldn't test against the value of the outer variable.
|
|
|
|
Here shows how we can use match guard to fix this problem.
|
|
```rust
|
|
fn main() {
|
|
let x = Some(5);
|
|
let y = 10;
|
|
|
|
match x {
|
|
Some(50) => println!("Got 50"),
|
|
Some(n) if n == y => println!("Matched, n = {n}"),
|
|
_ => println!("Default case, x = {x:?}"),
|
|
}
|
|
|
|
println!("at the end: x = {x:?}, y = {y}");
|
|
}
|
|
```
|
|
Output
|
|
```
|
|
Default case, x = Some(5)
|
|
at the end: x = Some(5), y = 10
|
|
```
|
|
The pattern in the second match arm doesn't introduce a new variable `y` that would shadow the outer `y`.
|
|
|
|
This means that we can use the outer `y` in the match guard.
|
|
|
|
Instead of specifying the pattern as `Some(y)`, which would have shadowed the outer `y`, we specify `Some(n)`.
|
|
|
|
This creates a new variable `n` that doesn't shadow anything because there is no `n` variable outside the `match`.
|
|
|
|
The match guard `if n == y` is not a pattern and therefore doesn't introduce new variables.
|
|
|
|
This `y` *is* the outer `y` rather than a new `y` shadowing it.
|
|
|
|
We can look for a value that has the same value as the outer `y` by comparing `n` to `y`.
|
|
|
|
You can also use the *or* operator `|` in a match guard to specify multiple patterns.
|
|
|
|
The match guard condition will apply to all the patterns.
|
|
|
|
Here shows the precedence when combining a pattern uses `|` with a match guard.
|
|
|
|
The important part of this example is that the `if y` match guard applies to `4`, `5`, and `6`, even though it might look like `if y` only applies to `6`.
|
|
```rust
|
|
let x = 4;
|
|
let y = false;
|
|
|
|
match x {
|
|
4 | 5 | 6 if y => println!("yes"),
|
|
_ => println!("no"),
|
|
}
|
|
```
|
|
This match condition states that the arm only matches if the if the value of `x` is equal to `4`, `5`, or `6` *and* if `y` is `true`.
|
|
|
|
When this runs, the pattern of the first arm matches because `x` is `4`, but the match guard `if y` is false, so the first arm is not chosen.
|
|
|
|
The code then moves on to the second arm, which does match and this program prints `no`.
|
|
|
|
The reason is that the `if` condition applies to the whole pattern `4 | 5| 6`, not only to the last value `6`.
|
|
|
|
In other words, the precedence of a match guard in relation to a pattern behaves like this
|
|
```
|
|
(4 | 5 | 6) if y => ...
|
|
```
|
|
rather than this
|
|
```
|
|
4 | 5 | (6 if y) => ...
|
|
```
|
|
After running this, the precedence behavior is evident.
|
|
|
|
If the match guard applied only to the final value in the list of values specified using the `|` operator, the arm would have matched and the program would have printed `yes`.
|
|
|
|
## `@` Bindings
|
|
The *at* operator `@` lets us create a variable that holds a value at the same time as we are testing that value for a pattern match.
|
|
|
|
Here we want to test that a `Message::Hello` `id` field is within the range `3..=7`.
|
|
|
|
We also want to bind that value to the variable `id_variable` so we can use it in the code associated with the arm.
|
|
|
|
We could name this variable `id`, the same as the field, but for this example we will use a different name.
|
|
```rust
|
|
enum Message {
|
|
Hello { id: i32 },
|
|
}
|
|
|
|
let msg = Message::Hello { id: 5 };
|
|
|
|
match msg {
|
|
Message::Hello {
|
|
id: id_variable @ 3..=7,
|
|
} => println!("Found an id in range: {id_variable}"),
|
|
Message::Hello { id: 10..=12 } => {
|
|
println!("Found an id in another range")
|
|
}
|
|
Message::Hello { id } => println!("Found some other id: {id}"),
|
|
}
|
|
```
|
|
Output
|
|
```
|
|
Found an id in range: 5
|
|
```
|
|
By specifying `id_vaariable @` before the range `3..=7`, we are capturing whatever value matched the range while also testing that the value matched the range pattern.
|
|
|
|
In the second arm, where we only have a range specified in the pattern, the code associated with the arm doesn't have a variable that contains the actual value of the `id` field.
|
|
|
|
The `id` field's values could have been 10, 11, or 12, but the code that goes with the pattern doesn't know which it is.
|
|
|
|
The pattern code isn't able to use the value form the `id` field, this is due to us not saving the `id` value in a variable.
|
|
|
|
In the last arm, where we have specified a variable without a range, we do have the value available to use in the arm's code in the variable named `id`.
|
|
|
|
The reason is that we have used the struct field shorthand syntax.
|
|
|
|
But we haven't applied any test to the value in the `id` field in this arm, as we did with the first two arms.
|
|
|
|
Any value would match this pattern.
|
|
|
|
Using `@` lets us test a value and save it in a variable within one pattern. |