Все случаи, где могут быть использованы шаблоны

В процессе использования языка Rust вы часто используете шаблоны, даже не осознавая этого! В этом разделе обсуждаются все случаи, где использование шаблонов является корректным.

Ветки match

Как обсуждалось в главе 6, мы используем шаблоны в ветках выражений match. Формально выражения match определяется как ключевое слово match, значение используемое для сопоставления, одна или несколько веток, которые состоят из шаблона и выражения для выполнения, если значение соответствует шаблону этой ветки, как здесь:

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}

Например, вот выражение match из листинга 6-5, которое соответствует значению Option<i32> в переменной x:

match x {
    None => None,
    Some(i) => Some(i + 1),
}

Шаблонами в этом выражении match являются None и Some(i) слева от каждой стрелки.

Одно из требований к выражениям match состоит в том, что они должны быть исчерпывающими (exhaustive) в том смысле, что они должны учитывать все возможности для значения в выражении match. Один из способов убедиться, что вы рассмотрели каждую возможность - это иметь шаблон перехвата всех вариантов в последней ветке выражения: например, имя переменной, совпадающее с любым значением, никогда не может потерпеть неудачу и таким образом, охватывает каждый оставшийся случай.

Специальный шаблон _ будет соответствовать чему угодно, но он никогда не привязывается к переменной, поэтому он часто используется в последней ветке. Шаблон _ может быть полезен, если вы, например, хотите игнорировать любое не указанное значение. Мы рассмотрим шаблон _ более подробно в разделе "Игнорирование значений в шаблоне позже в этой главе.

Условные выражения if let

В главе 6 мы обсуждали, как использовать выражения if let как правило в качестве более короткого способа записи эквивалента match, которое обрабатывает только один случай. Дополнительно if let может иметь соответствующий else, содержащий код для выполнения, если шаблон выражения if let не совпадает.

В листинге 18-1 показано, что можно также смешивать и сопоставлять выражения if let, else if и else if let. Это даёт больше гибкости, чем match выражение, в котором можно выразить только одно значение для сравнения с шаблонами. Кроме того, условия в серии if let, else if, else if let не обязаны соотноситься друг с другом.

Код в листинге 18-1 показывает последовательность проверок нескольких условий, определяющих каким должен быть цвет фона. В данном примере мы создали переменные с предопределёнными значениями, которые в реальной программе могли бы быть получены из пользовательского ввода.

Файл: src/main.rs


#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-01/src/main.rs}}
}

Листинг 18-1: Использование условных конструкций if let, else if, else if let, и else

Если пользователь указывает любимый цвет, то этот цвет используется в качестве цвета фона. Если любимый цвет не указан, и сегодня вторник, то цвет фона - зелёный. Иначе, если пользователь указывает свой возраст в виде строки, и мы можем успешно проанализировать её и представить в виде числа, то цвет будет либо фиолетовым, либо оранжевым, в зависимости от значения числа. Если ни одно из этих условий не выполняется, то цвет фона будет синим.

Эта условная структура позволяет поддерживать сложные требования. С жёстко закодированными значениями, которые у нас здесь есть, этот пример напечатает Using purple as the background color.

Можно увидеть, что if let может также вводить затенённые переменные, как это можно сделать в match ветках: строка if let Ok(age) = age вводит новую затенённую переменную age, которая содержит значение внутри варианта Ok. Это означает, что нам нужно поместить условие if age > 30 внутри этого блок: мы не можем объединить эти два условия в if let Ok(age) = age && age > 30. Затенённый age, который мы хотим сравнить с 30, не является действительным, пока не начнётся новая область видимости с фигурной скобки.

Недостатком использования if let выражений является то, что компилятор не проверяет полноту (exhaustiveness) всех вариантов, в то время как с помощью выражения match это происходит. Если мы не напишем последний блок else и, благодаря этому, пропустим обработку некоторых случаев, компилятор не предупредит нас о возможной логической ошибке.

Условные циклы while let

Аналогично конструкции if let, конструкция условного цикла while let позволяет повторять цикл while до тех пор, пока шаблон продолжает совпадать. Пример в листинге 18-2 демонстрирует цикл while let, который использует вектор в качестве стека и печатает значения вектора в порядке, обратном тому, в котором они были помещены.


#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-02/src/main.rs:here}}
}

Листинг 18-2: Использование цикла while let для печати значений до тех пор, пока stack.pop() возвращает Some

В этом примере выводится 3, 2, а затем 1. Метод pop извлекает последний элемент из вектора и возвращает Some(value). Если вектор пуст, то pop возвращает None. Цикл while продолжает выполнение кода в своём блоке, пока pop возвращает Some. Когда pop возвращает None, цикл останавливается. Мы можем использовать while let для удаления каждого элемента из стека.

Цикл for

В цикле for значение, которое следует непосредственно за ключевым словом for , является шаблоном. Например, в for x in y выражение x является шаблоном. В листинге 18-3 показано, как использовать шаблон в цикле for , чтобы деструктурировать или разбить кортеж как часть цикла for .


#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-03/src/main.rs:here}}
}

Листинг 18-3: Использование шаблона в цикле for для деструктурирования кортежа

Код в листинге 18-3 выведет следующее:

{{#include ../listings/ch18-patterns-and-matching/listing-18-03/output.txt}}

Мы адаптируем итератор с помощью метода enumerate, чтобы он генерировал кортеж, состоящий из значения и индекса этого значения. Первым сгенерированным значением будет кортеж (0, 'a'). Когда это значение сопоставляется с шаблоном (index, value), index будет равен 0, а value будет равно 'a' и будет напечатана первая строка выходных данных.

Инструкция let

До этой главы мы подробно обсуждали только использование шаблонов с match и if let, но на самом деле, мы использовали шаблоны и в других местах, в том числе в инструкциях let. Например, рассмотрим следующее простое назначение переменной с помощью let:


#![allow(unused)]
fn main() {
let x = 5;
}

Каждый раз, когда вы использовали подобным образом инструкцию let, вы использовали шаблоны, хотя могли и не осознавать этого! Более формально инструкция let выглядит так:

let PATTERN = EXPRESSION;

В инструкциях типа let x = 5; с именем переменной в слоте PATTERN, имя переменной является просто отдельной, простой формой шаблона. Rust сравнивает выражение с шаблоном и присваивает любые имена, которые он находит. Так что в примере let x = 5;, x - это шаблон, который означает "привязать то, что соответствует здесь, переменной x". Поскольку имя x является полностью шаблоном, этот шаблон фактически означает "привязать все к переменной x независимо от значения".

Чтобы более чётко увидеть аспект сопоставления с шаблоном let, рассмотрим листинг 18-4, в котором используется шаблон с let для деструктурирования кортежа.


#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-04/src/main.rs:here}}
}

Листинг 18-4. Использование шаблона для деструктуризации кортежа и создания трёх переменных одновременно

Здесь мы сопоставляем кортеж с шаблоном. Rust сравнивает значение (1, 2, 3) с шаблоном (x, y, z) и видит, что значение соответствует шаблону, поэтому Rust связывает 1 с x, 2 с y и 3 с z. Вы можете думать об этом шаблоне кортежа как о вложении в него трёх отдельных шаблонов переменных.

Если количество элементов в шаблоне не совпадает с количеством элементов в кортеже, то весь тип не будет совпадать и мы получим ошибку компилятора. Например, в листинге 18-5 показана попытка деструктурировать кортеж с тремя элементами в две переменные, что не будет работать.

{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-05/src/main.rs:here}}

Листинг 18-5: Неправильное построение шаблона, переменные не соответствуют количеству элементов в кортеже

Попытка скомпилировать этот код приводит к ошибке:

{{#include ../listings/ch18-patterns-and-matching/listing-18-05/output.txt}}

Чтобы исправить ошибку, мы могли бы игнорировать одно или несколько значений в кортеже, используя _ или .., как вы увидите в разделе “Игнорирование значений в Шаблоне” . Если шаблон содержит слишком много переменных в шаблоне, можно решить проблему, сделав типы совпадающими, удалив некоторые переменные таким образом, чтобы число переменных равнялось числу элементов в кортеже.

Параметры функции

Параметры функции также могут быть шаблонами. Код в листинге 18-6 объявляет функцию с именем foo, которая принимает один параметр с именем x типа i32, к настоящему времени это должно выглядеть знакомым.


#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-06/src/main.rs:here}}
}

Листинг 18-6: Сигнатура функции использует шаблоны в параметрах

x это часть шаблона! Как и в случае с let, мы можем сопоставить кортеж в аргументах функции с шаблоном. Листинг 18-7 разделяет значения в кортеже при его передачи в функцию.

Файл: src/main.rs


#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-07/src/main.rs}}
}

Листинг 18-7: Функция с параметрами, которая разрушает кортеж

Этот код печатает Current location: (3, 5). Значения &(3, 5) соответствуют шаблону &(x, y), поэтому x - это значение 3, а y - это значение 5.

Добавляя к вышесказанному, мы можем использовать шаблоны в списках параметров замыкания таким же образом, как и в списках параметров функции, потому что, как обсуждалось в главе 13, замыкания похожи на функции.

На данный момент вы видели несколько способов использования шаблонов, но шаблоны работают не одинаково во всех местах, где их можно использовать. В некоторых местах шаблоны должны быть неопровержимыми; в других обстоятельствах они могут быть опровергнуты. Мы обсудим эти две концепции далее.