mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-14 20:44:17 -06:00
finished ch19.3 and ch19
This commit is contained in:
parent
730b0091e8
commit
295d603cbd
2
.obsidian/graph.json
vendored
2
.obsidian/graph.json
vendored
@ -1,6 +1,6 @@
|
||||
{
|
||||
"collapse-filter": false,
|
||||
"search": "more information than the compiler",
|
||||
"search": "",
|
||||
"showTags": false,
|
||||
"showAttachments": false,
|
||||
"hideUnresolved": false,
|
||||
|
22
.obsidian/workspace.json
vendored
22
.obsidian/workspace.json
vendored
@ -49,6 +49,20 @@
|
||||
"title": "Pattern Matching"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "de2ac5df5b921166",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Advanced Features.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Advanced Features"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "78cd7043d5d51ffa",
|
||||
"type": "leaf",
|
||||
@ -102,7 +116,7 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"currentTab": 5
|
||||
"currentTab": 3
|
||||
}
|
||||
],
|
||||
"direction": "vertical"
|
||||
@ -245,12 +259,13 @@
|
||||
"command-palette:Open command palette": false
|
||||
}
|
||||
},
|
||||
"active": "6d24bd7ca73ed503",
|
||||
"active": "de2ac5df5b921166",
|
||||
"lastOpenFiles": [
|
||||
"Pattern Matching.md",
|
||||
"Advanced Features.md",
|
||||
"Places Patterns Can Be Used.md",
|
||||
"Pattern Syntax.md",
|
||||
"Refutability.md",
|
||||
"Places Patterns Can Be Used.md",
|
||||
"Implementing OO Design Pattern.md",
|
||||
"Trait Objects that allow for Values of Different Types.md",
|
||||
"Characteristics of OO Languages.md",
|
||||
@ -272,7 +287,6 @@
|
||||
"Test Controls.md",
|
||||
"Ref Cell Mutability.md",
|
||||
"Reference Counter Smart Pointer.md",
|
||||
"The Performance Closures and Iterators.md",
|
||||
"minigrep/src/lib.rs",
|
||||
"does_not_compile.svg",
|
||||
"Untitled.canvas",
|
||||
|
1
Advanced Features.md
Normal file
1
Advanced Features.md
Normal file
@ -0,0 +1 @@
|
||||
# Advanced Features
|
@ -30,5 +30,15 @@ This chapter is intended to be a reference on all things related to patterns.
|
||||
We will cover:
|
||||
- Valid places to use patterns [Section Link Here]()
|
||||
- Difference between refutable and irrefutable patterns [Section Link Here](./Refutability.md)
|
||||
- different kinds of pattern syntax [Section Link Here](./Pattern%20Syntax.md)
|
||||
By the end you will know how to use patterns to express many concepts in a clear way.
|
||||
- Different kinds of pattern syntax [Section Link Here](./Pattern%20Syntax.md)
|
||||
By the end you will know how to use patterns to express many concepts in a clear way.
|
||||
|
||||
## Summary
|
||||
|
||||
Patterns are very useful in distinguishing between different kinds of data.
|
||||
|
||||
When used in `match` expressions, Rust ensures your patterns cover every possible value, or your program will not compile.
|
||||
|
||||
Patterns in `let` statements and function parameters make those constructs more useful, enabling the destructuring of values into smaller parts at the same time as assigning to variables.
|
||||
|
||||
Then we can create simple or complex patterns to suit our needs.
|
@ -254,3 +254,433 @@ 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.
|
Loading…
x
Reference in New Issue
Block a user