finished ch19.3 and ch19
All checks were successful
Test Gitea Actions / first (push) Successful in 16s
Test Gitea Actions / check-code (push) Successful in 16s
Test Gitea Actions / test (push) Successful in 16s
Test Gitea Actions / documentation-check (push) Successful in 15s

This commit is contained in:
darkicewolf50 2025-04-10 16:47:49 -06:00
parent 730b0091e8
commit 295d603cbd
5 changed files with 462 additions and 7 deletions

View File

@ -1,6 +1,6 @@
{ {
"collapse-filter": false, "collapse-filter": false,
"search": "more information than the compiler", "search": "",
"showTags": false, "showTags": false,
"showAttachments": false, "showAttachments": false,
"hideUnresolved": false, "hideUnresolved": false,

View File

@ -49,6 +49,20 @@
"title": "Pattern Matching" "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", "id": "78cd7043d5d51ffa",
"type": "leaf", "type": "leaf",
@ -102,7 +116,7 @@
} }
} }
], ],
"currentTab": 5 "currentTab": 3
} }
], ],
"direction": "vertical" "direction": "vertical"
@ -245,12 +259,13 @@
"command-palette:Open command palette": false "command-palette:Open command palette": false
} }
}, },
"active": "6d24bd7ca73ed503", "active": "de2ac5df5b921166",
"lastOpenFiles": [ "lastOpenFiles": [
"Pattern Matching.md", "Pattern Matching.md",
"Advanced Features.md",
"Places Patterns Can Be Used.md",
"Pattern Syntax.md", "Pattern Syntax.md",
"Refutability.md", "Refutability.md",
"Places Patterns Can Be Used.md",
"Implementing OO Design Pattern.md", "Implementing OO Design Pattern.md",
"Trait Objects that allow for Values of Different Types.md", "Trait Objects that allow for Values of Different Types.md",
"Characteristics of OO Languages.md", "Characteristics of OO Languages.md",
@ -272,7 +287,6 @@
"Test Controls.md", "Test Controls.md",
"Ref Cell Mutability.md", "Ref Cell Mutability.md",
"Reference Counter Smart Pointer.md", "Reference Counter Smart Pointer.md",
"The Performance Closures and Iterators.md",
"minigrep/src/lib.rs", "minigrep/src/lib.rs",
"does_not_compile.svg", "does_not_compile.svg",
"Untitled.canvas", "Untitled.canvas",

1
Advanced Features.md Normal file
View File

@ -0,0 +1 @@
# Advanced Features

View File

@ -30,5 +30,15 @@ This chapter is intended to be a reference on all things related to patterns.
We will cover: We will cover:
- Valid places to use patterns [Section Link Here]() - Valid places to use patterns [Section Link Here]()
- Difference between refutable and irrefutable patterns [Section Link Here](./Refutability.md) - Difference between refutable and irrefutable patterns [Section Link Here](./Refutability.md)
- different kinds of pattern syntax [Section Link Here](./Pattern%20Syntax.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. 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.

View File

@ -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. The number of variables in the pattern must match the number of elements in the variant we are matching.
### Destructuring Nested Structs and Enums ### 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.