Все места, где можно использовать шаблоны
Шаблоны появляются в Rust во многих местах, и вы уже часто использовали их, не осознавая этого! В этом разделе обсуждаются все места, где шаблоны допустимы.
Ветви match
Как обсуждалось в главе 6, мы используем шаблоны в ветвях выражений match.
Формально выражения 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 состоит в том, что они должны быть
исчерпывающими: все возможности для значения в выражении match должны быть
учтены. Один способ убедиться, что вы покрыли каждую возможность, — иметь
универсальный шаблон для последней ветви. Например, имя
переменной, соответствующее любому значению, никогда не может дать сбой и
поэтому покрывает все оставшиеся случаи.
Конкретный шаблон _ соответствует чему угодно, но никогда не привязывается к
переменной, поэтому его часто используют в последней ветви match. Шаблон _
может быть полезен, когда вы хотите проигнорировать любое неуказанное
значение, например. Мы подробнее рассмотрим шаблон _ в разделе
«Игнорирование значений в шаблоне» позже в этой главе.
Операторы 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, рассмотрим
листинг 19-1, где шаблон используется с let для деструктурирования кортежа.
fn main() {
let (x, y, z) = (1, 2, 3);
}
Здесь мы сопоставляем кортеж с шаблоном. Rust сравнивает значение (1, 2, 3)
с шаблоном (x, y, z) и видит, что значение соответствует шаблону, то есть
что число элементов одинаково в обоих. Поэтому Rust привязывает 1 к x, 2
к y и 3 к z. Этот шаблон кортежа можно воспринимать как вложение трех
отдельных шаблонов переменных внутрь него.
Если число элементов в шаблоне не совпадает с числом элементов в кортеже, общий тип не совпадет, и мы получим ошибку компилятора. Например, листинг 19-2 показывает попытку деструктурировать кортеж из трех элементов в две переменные; это не сработает.
fn main() {
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` (bin "patterns") due to 1 previous error
Чтобы исправить ошибку, мы могли бы проигнорировать одно или несколько
значений в кортеже с помощью _ или .., как вы увидите в разделе
«Игнорирование значений в шаблоне». Если проблема в том, что у нас слишком много переменных в шаблоне,
решение состоит в том, чтобы привести типы в соответствие, удалив переменные
так, чтобы число переменных равнялось числу элементов в кортеже.
Условные выражения if let
В главе 6 мы обсуждали, как использовать выражения if let в основном как
более короткий способ записать эквивалент match, который сопоставляет только
один случай. При желании у if let может быть соответствующий else,
содержащий код для выполнения, если шаблон в if let не совпадет.
Листинг 19-3 показывает, что также можно сочетать выражения if let,
else if и else if let. Это дает нам больше гибкости, чем выражение
match, в котором мы можем выразить только одно значение для сравнения с
шаблонами. Кроме того, Rust не требует, чтобы условия в последовательности
ветвей if let, else if и else if let были связаны друг с другом.
Код в листинге 19-3 определяет, какой цвет сделать фоном, на основе серии проверок нескольких условий. Для этого примера мы создали переменные с жестко заданными значениями, которые реальная программа могла бы получить из пользовательского ввода.
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
if let Some(color) = favorite_color {
println!("Using your favorite color, {color}, as the background");
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}
if let, else if, else if let и elseЕсли пользователь укажет любимый цвет, этот цвет используется как фон. Если любимый цвет не указан и сегодня вторник, цвет фона будет зеленым. В противном случае, если пользователь укажет свой возраст как строку и мы можем успешно разобрать ее как число, цвет будет либо фиолетовым, либо оранжевым в зависимости от значения числа. Если ни одно из этих условий не применяется, цвет фона будет синим.
Эта условная структура позволяет нам поддерживать сложные требования. С
жестко заданными значениями, которые есть здесь, этот пример напечатает
Using purple as the background color.
Вы можете видеть, что if let также может вводить новые переменные, которые
затеняют существующие переменные так же, как это могут делать ветви match:
строка if let Ok(age) = age вводит новую переменную age, содержащую
значение внутри варианта Ok, затеняя существующую переменную age. Это
означает, что нам нужно поместить условие if age > 30 внутри этого блока: мы
не можем объединить эти два условия в if let Ok(age) = age && age > 30.
Новая переменная age, которую мы хотим сравнить с 30, недействительна до
начала новой области видимости с фигурной скобкой.
Недостаток использования выражений if let в том, что компилятор не проверяет
исчерпываемость, тогда как с выражениями match он это делает. Если бы мы
пропустили последний блок else и тем самым не обработали некоторые случаи,
компилятор не предупредил бы нас о возможной логической ошибке.
Условные циклы while let
Похожий по конструкции на if let, условный цикл while let позволяет циклу
while выполняться до тех пор, пока шаблон продолжает совпадать. В листинге
19-4 мы показываем цикл while let, который ожидает сообщения, отправленные
между потоками, но в этом случае проверяет Result вместо Option.
fn main() {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
for val in [1, 2, 3] {
tx.send(val).unwrap();
}
});
while let Ok(value) = rx.recv() {
println!("{value}");
}
}
while let для печати значений до тех пор, пока rx.recv() возвращает OkЭтот пример печатает 1, 2, а затем 3. Метод recv берет первое
сообщение из принимающей стороны канала и возвращает Ok(value). Когда мы
впервые увидели recv в главе 16, мы напрямую разворачивали ошибку или
взаимодействовали с ним как с итератором с помощью цикла for. Однако, как
показывает листинг 19-4, мы также можем использовать while let, потому что
метод recv возвращает Ok каждый раз, когда приходит сообщение, пока
отправитель существует, а затем возвращает Err, когда отправляющая сторона
отсоединяется.
Циклы for
В цикле for значение, которое непосредственно следует за ключевым словом
for, является шаблоном. Например, в for x in y x — это шаблон. Листинг
19-5 демонстрирует, как использовать шаблон в цикле for, чтобы
деструктурировать, или разобрать на части, кортеж как часть цикла for.
fn main() {
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{value} is at index {index}");
}
}
for для деструктурирования кортежаКод в листинге 19-5 напечатает следующее:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2
Мы адаптируем итератор с помощью метода enumerate, чтобы он создавал
значение и индекс для этого значения, помещенные в кортеж. Первое созданное
значение — это кортеж (0, 'a'). Когда это значение сопоставляется с шаблоном
(index, value), index будет 0, а value будет 'a', что напечатает
первую строку вывода.
Параметры функций
Параметры функций также могут быть шаблонами. Код в листинге 19-6, который
объявляет функцию с именем foo, принимающую один параметр с именем x типа
i32, к этому моменту должен выглядеть знакомо.
fn foo(x: i32) {
// code goes here
}
fn main() {}
Часть x — это шаблон! Как мы делали с let, мы могли бы сопоставить кортеж
в аргументах функции с шаблоном. Листинг 19-7 разделяет значения в кортеже,
когда мы передаем его в функцию.
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
Этот код печатает Current location: (3, 5). Значения &(3, 5) соответствуют
шаблону &(x, y), поэтому x получает значение 3, а y — значение 5.
Мы также можем использовать шаблоны в списках параметров замыканий так же, как в списках параметров функций, потому что замыкания похожи на функции, как обсуждалось в главе 13.
К этому моменту вы увидели несколько способов использования шаблонов, но шаблоны работают не одинаково во всех местах, где мы можем их использовать. В некоторых местах шаблоны должны быть неопровержимыми; в других обстоятельствах они могут быть опровержимыми. Далее мы обсудим эти две концепции.