The authors of the book are part of the language development team, which means that you will receive all the information first-hand - from installing the language to creating reliable and scalable programs. From creating functions, choosing data types, and binding variables, you will move on to more complex concepts:
- Ownership and borrowing, life cycle and types.
- Guaranteed software security.
- Testing, error handling, and effective refactoring.
- Generics, smart pointers, multithreading, trait objects, and mappings.
- Work with the built-in package manager Cargo to build, test, document code and manage dependencies.
- Advanced tools for working with Unsafe Rust.
You'll find lots of code examples, as well as three chapters on building complete projects to solidify knowledge: guessing games, building a command line tool, and a multi-threaded server.
Who is this book for
We assume that you wrote your code in a different programming language, but we make no assumptions about which one. We have tried to make this material accessible to those with a wide range of programming skills. We will not waste time talking about what programming is. If you are an absolute beginner in programming, then first read the introduction to programming.
How to use this book
-, , , . , ; .
: . . , , . 2, 12 20 , — .
1 , Rust, «Hello, World!» Cargo. 2 Rust. , . , . 3, Rust, , 4 Rust. , , 2, 3, 2, . , .
5 , 6 , match if let. Rust .
7 (API). 8 , , , -. 9 .
10 , , , . 11 , Rust . 12 grep, . , .
13 — , . 14 Cargo . 15 , , , .
16 , Rust . 17 Rust - , , , .
18 , Rust. 19 , , Rust, , , .
20 , !
, . Rust, Rust, , , , Rust.
: -, ! - , , , . , , .
Rust — , : . , , . , , ! , , , , . , .
: . . , , . 2, 12 20 , — .
1 , Rust, «Hello, World!» Cargo. 2 Rust. , . , . 3, Rust, , 4 Rust. , , 2, 3, 2, . , .
5 , 6 , match if let. Rust .
7 (API). 8 , , , -. 9 .
10 , , , . 11 , Rust . 12 grep, . , .
13 — , . 14 Cargo . 15 , , , .
16 , Rust . 17 Rust - , , , .
18 , Rust. 19 , , Rust, , , .
20 , !
, . Rust, Rust, , , , Rust.
: -, ! - , , , . , , .
Rust — , : . , , . , , ! , , , , . , .
Where patterns can be used
In Rust, patterns appear in a lot of places, and you've used them often without even realizing it! This section discusses situations in which patterns are valid.
Match expression branches
As discussed in Chapter 6, we use patterns in the branches of match expressions. Formally, match expressions are defined as the keyword match, then the value to match, and one or more branches of the match consisting of the pattern and the expression to be executed if the value matches the pattern of that branch, for example:
match {
=> ,
=> ,
=> ,
}
One of the requirements for match expressions is that they must be exhaustive in the sense that all possible values must be considered in match. For you to consider all possible options, you need to have an all-encompassing pattern in the last branch: for example, a variable name that matches any value will always work and thus cover all remaining cases.
The special pattern _ will match anything, but it doesn't bind to a variable and is therefore often used in the last sleeve of matching. The _ pattern is useful, for example, if you want to ignore any unspecified value. We will consider the pattern in more detail in the section “Ignoring values in a pattern”.
If let conditionals
In Chapter 6, we discussed if let statements mainly as a shorter way of writing the equivalent of a match expression that matches only one case. Alternatively, if let can have a matching else containing the code to execute if the pattern in the if let does not match.
Listing 18.1 shows that it is also possible to mix and match if let, else if, and else if let statements. This gives us more flexibility than using a match expression, which can only express one value to compare against patterns. In addition, conditions in a series of if let, else if, and else if let statements do not need to refer to each other.
The code in Listing 18.1 shows a series of tests for several conditions that decide what the background color should be. In this example, we created variables with hard-coded values that a real program could retrieve from user input.
Listing 18.1. Mixing if let, else if, else if let and else statements
src/main.rs
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
(1) if let Some(color) = favorite_color {
(2) println!(" , {}, ", color);
(3) } else if is_tuesday {
(4) println!(" - !");
(5) } else if let Ok(age) = age {
(6) if age > 30 {
(7) println!(" ");
} else {
(8) println!(" ");
}
(9) } else {
(10) println!(" ");
}
}
If the user specifies a favorite color (1), then this is the background color (2). If today is Tuesday (3), then the background color is green (4). If the user specifies his age as a string and we can successfully parse it as a number (5), then the color will be either purple (7) or orange (8) depending on the value of the number (6). If none of these conditions apply (9), then the background color is blue (10).
This conditional structure allows for complex requirements. With the hardcoded values here, this example would output
.
You can see that an if let expression can also introduce shaded variables in the same way as the sleeves of a match expression: the if let Ok (age) = age (5) line of code introduces a new shaded variable age that contains the value inside the Ok variant. This means that we need to put the condition if age> 30 in this block (6): we cannot combine these two conditions in the statement if let Ok (age) = age && age> 30. The shaded variable age that we want to compare with 30 , will be invalid until the new scope starts with a curly brace.
The disadvantage of using if let statements is that the compiler does not check for exhaustiveness, while it does for match statements. If we had skipped the last else (9) block and, therefore, the handling of some cases, the compiler would not have warned us about a possible logical error.
While let conditional loops
Similar in design to the if let statement, the while let conditional loop allows the while loop to run as long as the pattern matches. The example in Listing 18.2 shows a while let loop that uses a vector as a stack and outputs the values in the vector in the reverse order of the order in which they were added.
Listing 18.2. Using a while let loop to print values while stack.pop () returns Some
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
This example prints 3, 2, and then 1. The pop method takes the last element from the Vector and returns Some (value). If the vector is empty, then pop returns None. The while loop continues executing the code in its block until pop returns Some. When pop returns None, the loop stops. We can use a while let conditional loop to remove each item from the stack.
For loops
In Chapter 3, we mentioned that the for loop is the most common looping construct in Rust code, but we haven't discussed the pattern that for takes yet. In a for loop, the pattern is the value immediately following the for keyword, so in for x in y, the pattern is x.
Listing 18.3 shows the use of a pattern in a for loop to destructure, or decompose, a tuple within a for.
Listing 18.3. Using a pattern in a for loop to destructure a tuple
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{} {}", value, index);
}
The code in Listing 18.3 displays the following:
0
b 1
2
We use the enumerate method to rewrite the iterator to produce the value and index of that value in the iterator, placed in a tuple. The first call to the enumerate method produces a tuple (0, 'a'). When this value is combined with the (index, value) pattern, then index is 0 and value is 'a', the first row of data is output.
Let statements
Prior to this chapter, we directly discussed using patterns only with match and if let statements, but in fact we have used patterns elsewhere, including in let statements. Consider a simple way of passing a variable value using let:
let x = 5;
We've used this kind of let statements hundreds of times throughout this book, and while you may not have realized it, you've been using patterns! More formally, a let statement looks like this:
let = ;
In statements such as let x = 5; with a variable name in the PATTERN slot, the variable name is just a simple form of the pattern. Rust compares the expression to the pattern and assigns any names it finds. Therefore, in the example let x = 5; the pattern is x, which means "associate what matches here with the variable x". Since the name x represents the entire pattern, this pattern effectively means "bind everything to the variable x, whatever the value."
To see the mapping against the let statement pattern more clearly, consider Listing 18.4, which uses the let pattern to destructure a tuple.
Listing 18.4. Using a pattern to destructure a tuple and create three variables at once
let (x, y, z) = (1, 2, 3);
Here we are mapping a tuple to a pattern. Rust compares (1, 2, 3) to (x, y, z) and sees that this value matches the pattern, so Rust associates 1 with x, 2 with y, and 3 with z. You can think of this tuple pattern as nesting three separate variable patterns into it.
If the number of elements in the pattern does not match the number of elements in the tuple, then the aggregate type will not match and we will get a compiler error. For example, Listing 18.5 shows an attempt to destructure a three-tuple into two variables, which will not work.
Listing 18.5. Incorrect construction of the pattern, the variables of which do not coincide with the number of elements in the tuple
let (x, y) = (1, 2, 3);
Attempting to compile this code results in an error like:
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let (x, y) = (1, 2, 3);
| ^^^^^^ expected a tuple with 3 elements, found one with 2 elements
|
= note: expected type `({integer}, {integer}, {integer})`
found type `(_, _)`
If we wanted to ignore one or more values in a tuple, we could use _ or .., as you will see in the section “Ignoring values in a pattern”. If the problem is that there are too many variables in the pattern, then you need to make the types match by deleting the variables so that the number of variables equals the number of elements in the tuple.
Function parameters
Function parameters can also be patterns. The code in Listing 18.6 that declares a function foo that takes one parameter x of type i32 is now familiar to you.
Listing 18.6. Function signature uses patterns in parameters
fn foo(x: i32) {
//
}
Part x is a pattern! As with let, we can match the tuple in the function arguments to the pattern. Listing 18.7 breaks up the values in the tuple when we pass it inside the function.
Listing 18.7. Function with parameters that destructure the tuple
src/main.rs
fn print_coordinates(&(x, y): &(i32, i32)) {
println!(" : ({}, {})", x, y);
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
This code outputs
: (3, 5)
The values & (3, 5) match the pattern & (x, y), so x is 3 and y is 5.
In addition, we can use patterns in closure parameter lists in the same way as in function parameter lists, since closures are similar to functions, as described in Chapter 13.
You've already seen several ways to use patterns, but they don't work the same everywhere you can use them. In some situations, these patterns must be irrefutable; in others, they can be refutable. We will discuss these two concepts below.
Refutability: Possibility of pattern mismatch
Patterns come in two flavors: refutable and irrefutable. Patterns that will match any possible value passed are irrefutable. An example is the x in the let x = 5; statement, because x matches absolutely everything, and therefore cannot but match. Patterns that do not match some of the possible meanings are rebuttable. An example is Some (x) in the if let Some (x) = a_value statement, because if the value in a_value is None and not Some, then Some (x) will not match.
Function parameters, let statements, and for loops can only accept irrefutable patterns because the program cannot do anything meaningful when the values do not match. The if let and while let expressions only accept rebuttable patterns, because by definition they are designed to handle a possible error: the functionality of a conditional expression is its ability to perform different actions depending on success or failure.
In general, you shouldn't be concerned about the distinction between refutable and irrefutable patterns. However, you still need to be aware of the concept of rebuttability in order to react when you see it in an error message. In these cases, you will need to change either the pattern or the construct with which you are using the pattern, depending on the intended behavior of the code.
Let's take a look at what happens when we try to use an irrefutable pattern in a place where Rust requires an irrefutable pattern, and vice versa. Listing 18.8 shows a let statement, but for the pattern we specified Some (x), a rebuttable pattern. As you might expect, this code doesn't compile.
Listing 18.8. Trying to use a rebuttable pattern with let
let Some(x) = some_option_value;
If some_option_value were equal to None, then it would not coincide with the Some (x) pattern, that is, the pattern is refutable. However, a let statement can only accept an irrefutable pattern, since the code cannot do anything valid with the value None. At compile time, Rust will complain that we tried to use a rebuttable pattern where an irrefutable pattern is required:
error[E0005]: refutable pattern in local binding: `None` not covered
-->
|
3 | let Some(x) = some_option_value;
| ^^^^^^^ pattern `None` not covered
Since we have not (and could not have) covered every valid value with the Some (x) pattern, Rust is rightfully throwing a compiler error.
To fix the problem when we have a refutable pattern instead of an irrefutable one, we can change the code that uses the pattern: instead of let, we can use if let. Then, if the pattern does not match, then the code in curly braces will be skipped and the work will continue correctly. Listing 18.9 shows how to fix the code in Listing 18.8.
Listing 18.9. Using an if let statement and a rebuttable pattern block instead of let
if let Some(x) = some_option_value {
println!("{}", x);
}
The code is ready! This is absolutely correct code, although it means that we cannot use an irrefutable pattern without error. If we give the if let expression a pattern that always matches, such as x, as shown in Listing 18-10, then it will not compile.
Listing 18.10. Trying to use an irrefutable pattern with an if let statement
if let x = 5 {
println!("{}", x);
};
The compiler complains that it doesn't make sense to use an if let expression with an irrefutable pattern:
error[E0162]: irrefutable if-let pattern
--> <anon>:2:8
|
2 | if let x = 5 {
| ^ irrefutable pattern
For this reason, the sleeves of a match expression must use refutable patterns, with the exception of the last sleeve, which must match any remaining values against an irrefutable pattern. Rust allows the irrefutable pattern to be used in a match expression with only one sleeve, but this syntax is not particularly useful and can be replaced with a simpler let statement.
Now that you know where patterns are used and how refutable and irrefutable patterns differ, let's get acquainted with the syntax that we can use to create patterns.
About the authors
Steve Klabnik leads the Rust documentation team and is one of the key developers of the language. He is a frequent lecturer and writes a lot of open source code. Previously worked on projects such as Ruby and Ruby on Rails.
Carol Nichols is a member of the Rust Core development team and co-founder of Integer 32, LLC, the world's first Rust-focused software development consulting company. Nichols is the organizer of the Rust Belt conference on the Rust language.
»More details about the book can be found on the publisher's website
» Table of Contents
» Excerpt
For Habitants a 25% discount on coupon - Rust
Upon payment for the paper version of the book, an e-book is sent to the e-mail.