Материал: Язык программирования Rust

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам
Листинг 18-4. Использование шаблона для деструктуризации кортежа и создания трёх переменныходновременноЗдесь мы сопоставляем кортеж с шаблоном. Rust сравнивает значение (1, 2, 3) с шаблоном (x, y, z) и видит, что значение соответствует шаблону, поэтому Rust связывает 1 с x, 2 с y и 3 с z. Вы можете думать об этом шаблоне кортежа как о вложении в него трёх отдельных шаблонов переменных.Если количество элементов в шаблоне не совпадает с количеством элементов в кортеже,то весь тип не будет совпадать и мы получим ошибку компилятора. Например, в листинге 18-5 показана попытка деструктурировать кортеж с тремя элементами в две переменные, что не будет работать.Листинг 18-5: Неправильное построение шаблона, переменные не соответствуют количеству элементов вкортежеПопытка скомпилировать этот код приводит к ошибке:Чтобы исправить ошибку, мы могли бы игнорировать одно или несколько значений в кортеже, используя _ или , как вы увидите в разделе “Игнорирование значений вШаблоне” . Если шаблон содержит слишком много переменных в шаблоне, можно let (x, y, z) = (1, 2, 3); let (x, y) = (1, 2, 3); $ 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` due to previous error
решить проблему, сделав типы совпадающими, удалив некоторые переменные таким образом, чтобы число переменных равнялось числу элементов в кортеже.
Параметры функции
Параметры функции также могут быть образцами. Код в листинге 18-6 объявляет функцию с именем foo
, которая принимает один параметр с именем x
типа i32
, к настоящему времени это должно выглядеть знакомым.
Листинг 18-6: Сигнатура функции использует образцы в параметрах
x это часть шаблона! Как и в случае с let
, мы можем сопоставить кортеж в аргументах функции с образцом. Листинг 18-7 разделяет значения в кортеже при его передачи в функцию.
Файл: src/main.rs
Листинг 18-7: Функция с параметрами, которая разрушает кортеж
Этот код печатает текущие координаты: (3, 5)
. Значения
&(3, 5)
соответствуют образцу
&(x, y)
, поэтому x
- это значение
3
, а y
- это значение
5
Добавляя к вышесказанному, мы можем использовать шаблоны в списках параметров замыкания таким же образом, как и в списках параметров функции, потому что, как обсуждалось в главе 13, замыкания похожи на функции.
На данный момент вы видели несколько способов использования шаблонов, но шаблоны работают не одинаково во всех местах, где их можно использовать. В
некоторых местах шаблоны должны быть неопровержимыми; в других обстоятельствах они могут быть опровергнуты. Мы обсудим эти две концепции далее.
fn foo
(x: i32
) {
// code goes here
} fn print_coordinates
(&(x, y): &(
i32
, i32
)) { println!
(
"Current location: ({}, {})"
, x, y);
} fn main
() { let point = (
3
,
5
); print_coordinates(&point);
}

Возможность опровержения: может ли шаблон не
совпадать
Шаблоны бывают двух форм: опровержимые и неопровержимые. Шаблоны, которые будут соответствовать любому возможному переданному значению, являются
неопровержимыми (irrefutable). Примером может быть x
в выражении let x = 5;
,
потому что x
соответствует чему-либо и следовательно не может не совпадать.
Шаблоны, которые могут не соответствовать некоторому возможному значению,
являются опровержимыми (refutable). Примером может быть
Some(x)
в выражении if let Some(x) = a_value
, потому что если значение в переменной a_value равно
None
, а не
Some
, то шаблон
Some(x)
не будет совпадать.
Параметры функций, операторы let и for могут принимать только неопровержимые шаблоны, поскольку программа не может сделать ничего значимого, если значения не совпадают. А выражения if let и while let принимают опровержимые и неопровержимые шаблоны, но компилятор предостерегает от неопровержимых шаблонов, поскольку по определению они предназначены для обработки возможного сбоя: функциональность условного выражения заключается в его способности выполнять разный код в зависимости от успеха или неудачи.
В общем случае, вам не нужно беспокоиться о разнице между опровержимыми
(refutable) и неопровержимыми (irrefutable) образцами; тем не менее, вам необходимо ознакомиться с концепцией возможности опровержения, чтобы вы могли отреагировать на неё, увидев в сообщении об ошибке. В таких случаях вам потребуется изменить либо шаблон, либо конструкцию с которой вы используете шаблон в зависимости от предполагаемого поведения кода.
Давайте посмотрим на пример того, что происходит, когда мы пытаемся использовать опровержимый (refutable) шаблон, где Rust требует неопровержимый шаблон и наоборот. В листинге 18-8 показан оператор let
, но для образца мы указали
Some(x)
являющийся шаблоном, который можно опровергнуть. Как и следовало ожидать, этот код не будет компилироваться.
Листинг 18-8: Попытка использовать опровержимый шаблон вместе с
let
Если some_option_value было бы значением
None
, то оно не соответствовало бы шаблону
Some(x)
, что означает, что шаблон является опровержимым. Тем не менее,
оператор let может принимать только неопровержимый шаблон, потому что нет корректного кода, который может что-то сделать со значением
None
. Во время компиляции Rust будет жаловаться на то, что мы пытались использовать опровержимый шаблон для которого требуется неопровержимый шаблон:
let
Some
(x) = some_option_value;

Поскольку мы не покрыли (и не могли покрыть!) каждое допустимое значение с помощью образца
Some(x)
, то Rust выдаёт ошибку компиляции.
Чтобы исправить проблему наличия опровержимого шаблона, там где нужен неопровержимый шаблон, можно изменить код использующий шаблон: вместо использования let
, можно использовать if let
. Затем, если шаблон не совпадает,
выполнение кода внутри фигурных скобок будет пропущено, что даст возможность продолжить корректное выполнение. В листинге 18-9 показано, как исправить код из листинга 18-8.
Листинг 18-9. Использование
if let
и блока с опровергнутыми шаблонами вместо
let
Код сделан! Этот код совершенно корректный, хотя это означает, что мы не можем использовать неопровержимый образец без получения ошибки. Если мы используем шаблон if let
, который всегда будет совпадать, то для примера x
показанного в листинге 18-10, компилятор выдаст предупреждение.
Листинг 18-10. Попытка использовать неопровержимый шаблон с
if let
Rust жалуется, что не имеет смысла использовать, if let с неопровержимым образцом:
$
cargo run
Compiling patterns v0.1.0 (file:///projects/patterns) error[E0005]: refutable pattern in local binding: `None` not covered
-->
src/main.rs:3:9
|
3 | let Some(x) = some_option_value;
| ^^^^^^^ pattern `None` not covered
|
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an
`enum` with only one variant
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02- refutability.html note: `Option` defined here
= note: the matched value is of type `Option` help: you might want to use `if let` to ignore the variant that isn't matched
|
3 | let x = if let Some(x) = some_option_value { x } else { todo!() };
| ++++++++++ ++++++++++++++++++++++
For more information about this error, try `rustc --explain E0005`. error: could not compile `patterns` due to previous error if let
Some
(x) = some_option_value { println!
(
"{}"
, x);
} if let x =
5
{ println!
(
"{}"
, x);
};

По этой причине совпадающие рукава должны использовать опровержимые образцы, за исключением последнего, который должен сопоставлять любые оставшиеся значения с неопровержимым образцом. Rust позволяет нам использовать неопровержимый шаблон в match только с одним рукавом, но этот синтаксис не особенно полезен и может быть заменён более простым оператором let
Теперь, когда вы знаете, где использовать шаблоны и разницу между опровержимыми и неопровержимыми шаблонами, давайте рассмотрим весь синтаксис, который мы можем использовать для создания шаблонов.
$
cargo run
Compiling patterns v0.1.0 (file:///projects/patterns) warning: irrefutable `if let` pattern
-->
src/main.rs:2:8
|
2 | if let x = 5 {
| ^^^^^^^^^
|
= note: `#[warn(irrefutable_let_patterns)]` on by default
= note: this pattern will always match, so the `if let` is useless
= help: consider replacing the `if let` with a `let` warning: `patterns` (bin "patterns") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/patterns`
5

Синтаксис шаблонов
В этом разделе мы рассмотрим все допустимые выражения в шаблонах и обсудим, зачем и когда они могут пригодиться.
Сопоставление с литералом
Как мы уже видели в главе 6, можно сопоставлять с литералами напрямую. В следующем коде есть несколько примеров:
Этот код печатает one
, потому что значение в x
равно 1. Данный синтаксис полезен,
когда вы хотите, чтобы ваш код предпринял действие, если он получает конкретное значение.
Сопоставление именованных переменных
Именованные переменные - это неопровержимые (irrefutable) шаблоны, которые соответствуют любому значению и мы использовали их много раз в книге. Однако при использовании именованных переменных в выражениях match возникает сложность.
Поскольку match начинает новую область видимости, то переменные, объявленные как часть шаблона внутри выражения match
, будут затенять переменные с тем же именем вне конструкции match как и в случае со всеми переменными. В листинге 18-11 мы объявляем переменную с именем x
со значением
Some(5)
и переменную y
со значением
10
. Затем мы создаём выражение match для значения x
. Посмотрите на шаблоны в ветках, println!
в конце и попытайтесь выяснить, какой код будет напечатан прежде чем запускать его или читать дальше.
Файл: src/main.rs let x =
1
; match x {
1
=> println!
(
"one"
),
2
=> println!
(
"two"
),
3
=> println!
(
"three"
),
_ => println!
(
"anything"
),
}

Листинг 18-11: Выражение
match
с веткой, которая добавляет затенённую переменную
y
Давайте рассмотрим, что происходит, когда выполняется выражение match
. Шаблон в первой ветке не соответствует определённому значению x
, поэтому выполнение продолжается.
Шаблон во второй ветке вводит новую переменную с именем y
, которая будет соответствовать любому значению в
Some
. Поскольку мы находимся в новой области видимости внутри выражения match
, это новая переменная y
, а не y
которую мы объявили в начале со значением 10. Эта новая привязка y
будет соответствовать любому значению из
Some
, которое находится в x
. Следовательно, эта новая y
связывается с внутренним значением
Some из переменной x
. Этим значением является
5
, поэтому выражение для этой ветки выполняется и печатает
Matched, y = 5
Если бы x
было значением
None вместо
Some(5)
, то шаблоны в первых двух ветках не совпали бы, поэтому значение соответствовало бы подчёркиванию. Мы не ввели переменную x
в шаблоне ветки со знаком подчёркивания, поэтому x
в выражении все ещё является внешней переменной x
, которая не была затенена. В этом гипотетическом случае совпадение match выведет
Default case, x = None
Когда выражение match завершается, заканчивается его область видимости как и область действия внутренней переменной y
. Последний println!
печатает at the end: x = Some(5), y = 10
Чтобы создать выражение match
, которое сравнивает значения внешних x
и y
, вместо введения затенённой переменной нужно использовать условие в сопоставлении образца. Мы поговорим про условие в сопоставлении шаблона позже в разделе
“Дополнительные условия в сопоставлении образца”
Группа шаблонов
В выражениях match можно сравнивать сразу с несколькими шаблонами, используя синтаксис
|
, который является оператором паттерна or. Например, в следующем примере мы сопоставляем значение x
с ветвями match, первая из которых содержит 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 = {:?}, y = {y}"
, x);
оператор or, так что если значение x
совпадёт с любым из значений в этой ветви, то будет выполнен её код:
Будет напечатано one or two
Сопоставление диапазонов с помощью ..=
Синтаксис
..=
позволяет нам выполнять сравнение с диапазоном значений. В
следующем коде, когда в шаблоне найдётся совпадение с любым из значений заданного диапазона, будет выполнена эта ветка:
Если x
равен 1, 2, 3, 4 или 5, то совпадение будет достигнуто в первой ветке. Этот синтаксис более удобен при указании нескольких значений для сравнения, чем использование оператора
|
для определения этой же идеи; если бы мы решили использовать
|
, нам пришлось бы написать
1 | 2 | 3 | 4 | 5
. Указание диапазона намного короче, особенно если мы хотим подобрать, скажем, любое число от 1 до 1 000!
Компилятор проверяет, что диапазон не является пустым во время компиляции, и поскольку единственными типами, для которых Rust может определить, пуст диапазон или нет, являются char и числовые значения, диапазоны допускаются только с числовыми или char значениями.
Вот пример использования диапазонов значений char
:
let x =
1
; match x {
1
|
2
=> println!
(
"one or two"
),
3
=> println!
(
"three"
),
_ => println!
(
"anything"
),
} let x =
5
; match x {
1
..=
5
=> println!
(
"one through five"
),
_ => println!
(
"something else"
),
} let x =
'c'
; match x {
'a'
..=
'j'
=> println!
(
"early ASCII letter"
),
'k'
..=
'z'
=> println!
(
"late ASCII letter"
),
_ => println!
(
"something else"
),
}

Rust может сообщить, что 'c'
находится в диапазоне первого шаблона и напечатать начальную букву ASCII
Деструктуризация для получения значений
Мы также можем использовать шаблоны для деструктуризации структур, перечислений и кортежей, чтобы использовать разные части этих значений. Давайте пройдёмся по каждому варианту.
Деструктуризация структуры
В листинге 18-12 показана структура
Point с двумя полями x
и y
, которые мы можем разделить, используя шаблон с выражением let
Файл: src/main.rs
Листинг 18-12: Разбиение полей структуры в отдельные переменные
Этот код создаёт переменные a
и b
, которые сопоставляются значениям полей x
и y
структуры p
. Этот пример показывает, что имена переменных в шаблоне не обязательно должны совпадать с именами полей структуры. Однако обычно имена переменных сопоставляются с именами полей, чтобы было легче запомнить, какие переменные взяты из каких полей. Из-за этого, а также из-за того, что строчка let Point
{ x: x, y: y } = p;
содержит много дублирования, в Rust ввели специальное сокращение для шаблонов, соответствующих полям структуры: вам нужно только указать имя поля структуры, и тогда переменные, созданные из шаблона, будут иметь те же имена. Код в листинге 18-13 аналогичен коду в Листинге 18-12, но в шаблоне let создаются переменные x
и y
, вместо a
и b
Файл: src/main.rs 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);
}
Источник: https://files.student-it.ru/previewfile/264058