Все случаи, где могут быть использованы шаблоны
В процессе использования языка 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, замыкания похожи на функции.
На данный момент вы видели несколько способов использования шаблонов, но шаблоны работают не одинаково во всех местах, где их можно использовать. В некоторых местах шаблоны должны быть неопровержимыми; в других обстоятельствах они могут быть опровергнуты. Мы обсудим эти две концепции далее.