mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 04:54:17 -06:00
finished ch19.1
This commit is contained in:
parent
c3ea7d26e3
commit
f0cbfdf024
16
.obsidian/workspace.json
vendored
16
.obsidian/workspace.json
vendored
@ -63,6 +63,20 @@
|
||||
"title": "Places Patterns Can Be Used"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "7cb3bb4674a93e14",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "Refutability.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "Refutability"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "6ed9f182aa3d5f3e",
|
||||
"type": "leaf",
|
||||
@ -220,6 +234,7 @@
|
||||
"active": "78cd7043d5d51ffa",
|
||||
"lastOpenFiles": [
|
||||
"Pattern Matching.md",
|
||||
"Refutability.md",
|
||||
"Places Patterns Can Be Used.md",
|
||||
"Implementing OO Design Pattern.md",
|
||||
"Trait Objects that allow for Values of Different Types.md",
|
||||
@ -244,7 +259,6 @@
|
||||
"Reference Counter Smart Pointer.md",
|
||||
"The Performance Closures and Iterators.md",
|
||||
"Improving The IO Project.md",
|
||||
"Tests.md",
|
||||
"minigrep/src/lib.rs",
|
||||
"does_not_compile.svg",
|
||||
"Untitled.canvas",
|
||||
|
@ -1 +1,254 @@
|
||||
# All the Places Patterns Can Be Used
|
||||
Patterns pop up in a number of places in Rust.
|
||||
|
||||
You use them a lot without realizing it.
|
||||
|
||||
## `match` Arms
|
||||
As discussed in Ch6, we can use patterns in the arms of `match` expressions.
|
||||
|
||||
`match` expressions are defined as the keyword `match`, a value to match on, and one or more match arms that consist of a pattern and an expression to run if the value matches that arm's pattern, like this:
|
||||
```
|
||||
match VALUE {
|
||||
PATTERN => EXPRESSION,
|
||||
PATTERN => EXPRESSION,
|
||||
PATTERN => EXPRESSION,
|
||||
}
|
||||
```
|
||||
Here is an example of a `match` expression from ch6 that matches on an `Option<i32>` value in the variable `x`:
|
||||
```rust
|
||||
match x {
|
||||
None => None,
|
||||
Some(i) => Some(i + 1),
|
||||
}
|
||||
```
|
||||
The patterns in here are the `None` and `Some(i)` on the left of each arrow.
|
||||
|
||||
One requirement for `match` expression is that they need to be *exhaustive* in the sense that all possibilities for the value in the `match` expression must be accounted for.
|
||||
|
||||
One way to ensure you have covered every possibility is to have a catchall pattern for the last arm.
|
||||
|
||||
For example, a variable name matching any value can never fail and thus covers every remaining case.
|
||||
|
||||
The particular pattern `_` will match anything, but it never binds to a variable, so it is often used in the last match arm.
|
||||
|
||||
The `_` pattern in more detail in the ["Ignoring Values in a Pattern"]() section.
|
||||
|
||||
## Conditional `if let` Expressions
|
||||
In Ch6 we discussed how to use `if let` expressions mainly as a shorter way to write the equivalent of a `match` that only matches o one case.
|
||||
|
||||
Optionally `if let` can have a corresponding `else` containing code to run if the pattern in the `if let` doesn't match.
|
||||
|
||||
In this example it shows that it is also possible to mix and match `if let`, `else if` and `else if let` expressions.
|
||||
|
||||
This gives us more flexibility than a `match` expression in which we can express only one value to compare with the patterns.
|
||||
|
||||
Rust doesn't require that the conditions in a series of `if let`, `else if`, `else if let` arms relate to each other.
|
||||
|
||||
This code determines what color to make your background based on a series of checks for several conditions.
|
||||
|
||||
For example, we have created variables with hardcoded values that a real program might receive form user input.
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let favorite_color: Option<&str> = None;
|
||||
let is_tuesday = false;
|
||||
let age: Result<u8, _> = "34".parse();
|
||||
|
||||
if let Some(color) = favorite_color {
|
||||
println!("Using your favorite color, {color}, as the background");
|
||||
} else if is_tuesday {
|
||||
println!("Tuesday is green day!");
|
||||
} else if let Ok(age) = age {
|
||||
if age > 30 {
|
||||
println!("Using purple as the background color");
|
||||
} else {
|
||||
println!("Using orange as the background color");
|
||||
}
|
||||
} else {
|
||||
println!("Using blue as the background color");
|
||||
}
|
||||
}
|
||||
```
|
||||
Output
|
||||
```
|
||||
Using purple as the background color
|
||||
```
|
||||
If the user specifies a favorite color, that color is used as the background.
|
||||
|
||||
If no color is specified and today is Tuesday, the background is green.
|
||||
|
||||
If the user specifies their age as a string and we can parse it as a number successfully, the color is either purple or orange depending on the value of the number.
|
||||
|
||||
|
||||
If none of the conditions apply, the background is blue.
|
||||
|
||||
This conditional structure lets us support complex requirements.
|
||||
|
||||
You can see that `if let` can also introduce new variables which shadow existing variables in the same way that `match` arms can.
|
||||
|
||||
The line `if let Ok(age) = age` introduces a new `age` variable that contains the `Ok` variant, shadowing the existing `age` variable.
|
||||
|
||||
This means we need to place the `if age > 30` condition within that block.
|
||||
|
||||
We cannot combine these two conditions into `if let Ok(age) = age && age > 30`.
|
||||
|
||||
The new `age` we want to compare to 30 isn't valid until the new scope starts with the curly bracket.
|
||||
|
||||
The downside of using `if let` expressions is that the compiler doesn't check for exhaustiveness, whereas with `match` expressions it does.
|
||||
|
||||
If we omitted the last `else` block and therefore missed handling some cases, the compiler would not alert us to the possible logic bug.
|
||||
|
||||
## `while let` Conditional Loops
|
||||
Similar to the construction of `if let`, the `while let` conditional loop allows a `while` loop to run for as long as a pattern continues to match.
|
||||
|
||||
We first saw this in Ch17, where we used it to keep looping as long as a stream produced new values.
|
||||
|
||||
Similarly here we show a `while let` loop that waits on messages sent between threads, but in this case checking a `Result` instead of an `Option`.
|
||||
```rust
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
for val in [1, 2, 3] {
|
||||
tx.send(val).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
while let Ok(value) = rx.recv() {
|
||||
println!("{value}");
|
||||
}
|
||||
```
|
||||
This example will print 1, 2, and 3.
|
||||
|
||||
When we saw `recv` back in Ch16, we unwrapped the error directly, or interacted with it as an iterator using a `for` loop.
|
||||
|
||||
Here shows we can also use `while let`, because the `recv` method returns `Ok` as long as the sender is producing messages and then produces an `Err` once the sender side disconnects.
|
||||
|
||||
## `for` Loops
|
||||
In a `for` loop, the value that directly follows the keyword `for` is a pattern.
|
||||
|
||||
This example demonstrates how to use a pattern in a `for` loop to destructure or break apart, a type as part of the `for` loop.
|
||||
```rust
|
||||
let v = vec!['a', 'b', 'c'];
|
||||
|
||||
for (index, value) in v.iter().enumerate() {
|
||||
println!("{value} is at index {index}");
|
||||
}
|
||||
```
|
||||
Output
|
||||
```rust
|
||||
$ cargo run
|
||||
Compiling patterns v0.1.0 (file:///projects/patterns)
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
|
||||
Running `target/debug/patterns`
|
||||
a is at index 0
|
||||
b is at index 1
|
||||
c is at index 2
|
||||
```
|
||||
We adapt an iterator using the `enumerate` method so it produces a values and the index for that value, placed into a tuple.
|
||||
|
||||
The first value produced is the tuple `(0, 'a')`.
|
||||
|
||||
When this value is matched to the patter `(index, value)`, `index` will be `0` and `value` will be `'a'`, printing the first line out the output.
|
||||
|
||||
## `let` Statements
|
||||
Prior to this ch, we only discussed using patterns with `match` and `if let`, but in fact, we have used patterns in other places as well.
|
||||
|
||||
This includes `let` statements.
|
||||
|
||||
For example consider this straightforward variable assignment with `let`
|
||||
```rust
|
||||
let x = 5;
|
||||
```
|
||||
Every time you used a `let` statement like this you have been using patterns, although you might not have realized it.
|
||||
|
||||
More formally, a `let` statement looks like this
|
||||
```
|
||||
let PATTERN = EXPRESSION;
|
||||
```
|
||||
In statements like `let x = 5;` with a variable name in the `PATTERN` slot.
|
||||
|
||||
The variable name is just a particularly simple form of a pattern.
|
||||
|
||||
Rust compares the expression against the pattern and assign any names it finds.
|
||||
|
||||
So in the `let x = 5;` example `x` is a pattern that means "bind what matches here to the variable `x`."
|
||||
|
||||
Because the name `x` is the whole pattern, this pattern effectively means "bind everything to the variable `x`, whatever the values is."
|
||||
|
||||
To see the pattern matching aspect of `let` more clearly, consider this, which uses a pattern with `let` to destructure a tuple.
|
||||
```rust
|
||||
let (x, y, z) = (1, 2, 3);
|
||||
```
|
||||
Here we match a tuple against a pattern.
|
||||
|
||||
Rust compares the value `(1, 2, 3)` to the pattern `(x, y, z)` and sees that the value matches the pattern.
|
||||
|
||||
Rust binds `1` to `x`, `2` to `y`, and `3` to `z`.
|
||||
|
||||
You can think of this tuple pattern as nesting three individual variable patterns inside it.
|
||||
|
||||
If the number of elements in the pattern doesn't match the number of elements in the tuple, the overall type won't match and we will get a compiler error.
|
||||
|
||||
Here shows an attempt to destructure a tuple with three elements into two variables, this will not work.
|
||||
|
||||
```rust
|
||||
let (x, y) = (1, 2, 3);
|
||||
```
|
||||
Here is the compiler error form attempting to compile
|
||||
```
|
||||
$ cargo run
|
||||
Compiling patterns v0.1.0 (file:///projects/patterns)
|
||||
error[E0308]: mismatched types
|
||||
--> src/main.rs:2:9
|
||||
|
|
||||
2 | let (x, y) = (1, 2, 3);
|
||||
| ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})`
|
||||
| |
|
||||
| expected a tuple with 3 elements, found one with 2 elements
|
||||
|
|
||||
= note: expected tuple `({integer}, {integer}, {integer})`
|
||||
found tuple `(_, _)`
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
||||
error: could not compile `patterns` (bin "patterns") due to 1 previous error
|
||||
```
|
||||
In order to fix this error, we could ignore one or more of the values in the tuple using `_` or `..`
|
||||
|
||||
You will see this in the ["Ignoring Values in a Pattern"]()
|
||||
|
||||
If the problem is that we have too many variables in the pattern, the solution is to make the types match by removing variables so the number equals the number of elements in the tuple.
|
||||
|
||||
## Function Parameters
|
||||
Function parameter can also be patterns.
|
||||
|
||||
The code here, which declares a function named `foo` that takes one parameter named `x` of type `i32`, should by now look familiar.
|
||||
```rust
|
||||
fn foo(x: i32) {
|
||||
// code goes here
|
||||
}
|
||||
```
|
||||
The `x` part is a pattern.
|
||||
|
||||
As we did with `let` we could match a tuple in a function's arguments to the pattern.
|
||||
|
||||
In this next example, this splits the values in a tuple as we pass it to a function.
|
||||
```rust
|
||||
fn print_coordinates(&(x, y): &(i32, i32)) {
|
||||
println!("Current location: ({x}, {y})");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let point = (3, 5);
|
||||
print_coordinates(&point);
|
||||
}
|
||||
```
|
||||
This code prints `Current loaction: (3, 5)`.
|
||||
|
||||
The values `&(3, 5)` match the pattern `&(x, y)`, so `x` is the value `3` and `y` is the value `5`.
|
||||
|
||||
We can also use patterns in closure parameter lists in the same way as in function parameter lists, because closure are similar to functions (discussed in Ch 13).
|
||||
|
||||
Now we have seen several ways of using patterns, but patterns don't work the same in every place we can use them.
|
||||
|
||||
In some places, the patterns must be irrefutable; other circumstances, they can be refutable.
|
||||
|
||||
This will be discussed in the next section [Here](./Refutability.md)
|
1
Refutability.md
Normal file
1
Refutability.md
Normal file
@ -0,0 +1 @@
|
||||
# Refutability: Whether a Pattern Might Fail to Match
|
Loading…
x
Reference in New Issue
Block a user