mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 13:04:18 -06:00
257 lines
9.5 KiB
Markdown
257 lines
9.5 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
|