Все случаи, где могут быть использованы шаблоны
В процессе использования языка 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}} }
Если пользователь указывает любимый цвет, то этот цвет используется в качестве цвета фона. Если любимый цвет не указан, и сегодня вторник, то цвет фона - зелёный. Иначе, если пользователь указывает свой возраст в виде строки, и мы можем успешно проанализировать её и представить в виде числа, то цвет будет либо фиолетовым, либо оранжевым, в зависимости от значения числа. Если ни одно из этих условий не выполняется, то цвет фона будет синим.
Эта условная структура позволяет поддерживать сложные требования. С жёстко закодированными значениями, которые у нас здесь есть, этот пример напечатает 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}} }
В этом примере выводится 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 выведет следующее:
{{#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}} }
Здесь мы сопоставляем кортеж с шаблоном. 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}}
Попытка скомпилировать этот код приводит к ошибке:
{{#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}} }
x
это часть шаблона! Как и в случае с let
, мы можем сопоставить кортеж в аргументах функции с шаблоном. Листинг 18-7 разделяет значения в кортеже при его передачи в функцию.
Файл: src/main.rs
#![allow(unused)] fn main() { {{#rustdoc_include ../listings/ch18-patterns-and-matching/listing-18-07/src/main.rs}} }
Этот код печатает Current location: (3, 5)
. Значения &(3, 5)
соответствуют шаблону &(x, y)
, поэтому x
- это значение 3
, а y
- это значение 5
.
Добавляя к вышесказанному, мы можем использовать шаблоны в списках параметров замыкания таким же образом, как и в списках параметров функции, потому что, как обсуждалось в главе 13, замыкания похожи на функции.
На данный момент вы видели несколько способов использования шаблонов, но шаблоны работают не одинаково во всех местах, где их можно использовать. В некоторых местах шаблоны должны быть неопровержимыми; в других обстоятельствах они могут быть опровергнуты. Мы обсудим эти две концепции далее.