Введение
Добро пожаловать в Все места, где можно использовать шаблоны. Этот лаба является частью Rust Book. Вы можете практиковать свои навыки Rust в LabEx.
В этом лабе мы исследуем все места, где шаблоны можно использовать в Rust.
This tutorial is from open-source community. Access the source code
💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал
Добро пожаловать в Все места, где можно использовать шаблоны. Этот лаба является частью Rust Book. Вы можете практиковать свои навыки Rust в LabEx.
В этом лабе мы исследуем все места, где шаблоны можно использовать в Rust.
Шаблоны встречаются в различных местах в Rust, и вы много их используете, даже не подозревая этого! В этом разделе обсуждаются все места, где шаблоны допустимы.
match
Как обсуждалось в главе 6, мы используем шаблоны в сетках (arms
) выражений match
. Формально, выражения match
определяются как ключевое слово match
, значение для сопоставления и одну или более сеток (arms
), которые состоят из шаблона и выражения, которое нужно выполнить, если значение соответствует шаблону этой сетки (arm
), вот так:
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
должны быть учтены. Одним из способов гарантировать, что вы охватили все возможности, является наличие шаблона "ловушка" для последней сетки (arm
): например, имя переменной, соответствующее любому значению, никогда не может неудасться и таким образом охватывает все оставшиеся случаи.
Особый шаблон _
будет соответствовать любому значению, но никогда не связывается с переменной, поэтому его часто используют в последней сетке (arm
) match
. Например, шаблон _
может быть полезен, когда вы хотите игнорировать любое значение, не указанное явно. Мы рассмотрим шаблон _
более подробно в разделе "Игнорирование значений в шаблоне".
В главе 6 мы обсуждали, как использовать выражения if let
в основном в качестве более короткого способа записи эквивалента match
, который соответствует только одному случаю. По желанию, if let
может иметь соответствующий else
, содержащий код, который нужно выполнить, если шаблон в if let
не соответствует.
Листинг 18-1 показывает, что также можно комбинировать if let
, else if
и else if let
выражения. Это дает нам больше гибкости, чем выражение match
, в котором мы можем выразить только одно значение для сравнения с шаблонами. Кроме того, Rust не требует, чтобы условия в серии if let
, else if
и else if let
сеток (arms
) были взаимосвязаны.
Код в Листинге 18-1 определяет, какой цвет сделать фоном на основе серии проверок нескольких условий. Для этого примера мы создали переменные с жестко заданными значениями, которые настоящий программа могла бы получить из ввода пользователя.
Filename: src/main.rs
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
1 if let Some(color) = favorite_color {
2 println!(
"Using your favorite, {color}, as the background"
);
3 } else if is_tuesday {
4 println!("Tuesday is green day!");
5 } else if let Ok(age) = age {
6 if age > 30 {
7 println!("Using purple as the background color");
} else {
8 println!("Using orange as the background color");
}
9 } else {
10 println!("Using blue as the background color");
}
}
Листинг 18-1: Комбинирование if let
, else if
, else if let
и else
Если пользователь укажет любимый цвет [1], этот цвет используется в качестве фона [2]. Если любимый цвет не указан и сегодня вторник [3], цвет фона - зеленый [4]. В противном случае, если пользователь укажет свой возраст в виде строки и мы сможем успешно его преобразовать в число [5], цвет будет либо фиолетовый [7], либо оранжевый [8], в зависимости от значения числа [6]. Если ни одно из этих условий не выполняется [9], цвет фона - синий [10].
Такая условная структура позволяет нам поддерживать сложные требования. С использованием жестко заданных значений здесь этот пример выведет Using purple as the background color
.
Вы можете видеть, что if let
также может вводить скрытые переменные так же, как и сетки (arms
) match
: строка if let Ok(age) = age
[5] вводит новую скрытую переменную age
, которая содержит значение внутри варианта Ok
. Это означает, что мы должны поместить условие if age > 30
[6] внутри этого блока: мы не можем объединить эти два условия в if let Ok(age) = age && age > 30
. Скрытая переменная age
, с которой мы хотим сравнивать 30, недействительна до начала нового скоупа, обозначенного фигурными скобками.
Недостатком использования выражений if let
является то, что компилятор не проверяет их на исчерпывающий характер, в то время как это делает для выражений match
. Если мы опустим последний блок else
[9] и, следовательно, упустим обработку некоторых случаев, компилятор не предупредит нас о возможной ошибке в логике.
Похожие по конструкции на if let
, условные циклы while let
позволяют циклу while
выполняться столько времени, сколько шаблон продолжает соответствовать. В Листинге 18-2 мы пишем цикл while let
, который использует вектор в качестве стека и выводит значения в векторе в обратном порядке, в котором они были добавлены.
Filename: src/main.rs
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{top}");
}
Листинг 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 x in y
x
- это шаблон. Листинг 18-3 демонстрирует, как использовать шаблон в цикле for
для деструктурирования, или разбиения, кортежа в рамках цикла for
.
Filename: src/main.rs
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{value} is at index {index}");
}
Листинг 18-3: Использование шаблона в цикле for
для деструктурирования кортежа
Код в Листинге 18-3 выведет следующее:
a is at index 0
b is at index 1
c is at index 2
Мы адаптируем итератор с использованием метода enumerate
, чтобы он генерировал значение и индекс для этого значения, помещенные в кортеж. Первое генерируемое значение - это кортеж (0, 'a')
. Когда это значение сопоставляется с шаблоном (index, value)
, index
будет равен 0
, а value
будет равен 'a'
, выводя первую строку результата.
До этой главы мы только явно обсуждали использование шаблонов с match
и if let
, но на самом деле мы использовали шаблоны и в других местах, в том числе и в let
- выражениях. Например, рассмотрим простое присвоение переменной с использованием let
:
let x = 5;
Каждый раз, когда вы использовали let
- выражение подобное этому, вы использовали шаблоны, хотя, возможно, и не замечали этого! Более формально, let
- выражение выглядит так:
let PATTERN = EXPRESSION;
В выражениях типа let x = 5;
, где в слоте PATTERN стоит имя переменной, имя переменной представляет собой особо простую форму шаблона. Rust сравнивает выражение с шаблоном и назначает любые имена, которые находит. Таким образом, в примере let x = 5;
x
- это шаблон, который означает "привяжите то, что здесь совпадает, к переменной x
". Поскольку имя x
представляет весь шаблон, этот шаблон по существу означает "привяжите все к переменной x
, независимо от значения".
Чтобы более четко увидеть аспекты сопоставления шаблонов в let
, рассмотрим Листинг 18 - 4, в котором используется шаблон с let
для деструктурирования кортежа.
let (x, y, z) = (1, 2, 3);
Листинг 18 - 4: Использование шаблона для деструктурирования кортежа и создания трех переменных сразу
Здесь мы сопоставляем кортеж с шаблоном. Rust сравнивает значение (1, 2, 3)
с шаблоном (x, y, z)
и видит, что значение соответствует шаблону, то есть количество элементов в обоих совпадает, поэтому Rust связывает 1
с x
, 2
с y
и 3
с z
. Можно представить этот шаблон кортежа, как вложение трех отдельных шаблонов переменных внутри него.
Если количество элементов в шаблоне не совпадает с количеством элементов в кортеже, тип в целом не совпадет и мы получим ошибку компиляции. Например, Листинг 18 - 5 показывает попытку деструктурирования кортежа с тремя элементами на две переменные, что не сработает.
let (x, y) = (1, 2, 3);
Листинг 18 - 5: Некорректное создание шаблона, в котором переменные не соответствуют количеству элементов в кортеже
Попытка скомпилировать этот код приводит к следующей ошибке типа:
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 `(_, _)`
Чтобы исправить ошибку, мы можем игнорировать один или несколько значений в кортеже с использованием _
или ..
, как вы увидите в разделе "Игнорирование значений в шаблоне". Если проблема заключается в том, что в шаблоне слишком много переменных, решение - это сделать типы совместимыми, удалив переменные, чтобы количество переменных было равно количеству элементов в кортеже.
Параметры функций также могут быть шаблонами. Код в Листинге 18 - 6, который объявляет функцию под названием foo
, которая принимает один параметр под названием x
типа i32
, наверняка уже выглядит знакомым.
fn foo(x: i32) {
// code goes here
}
Листинг 18 - 6: Сигнатура функции с использованием шаблонов в параметрах
Часть x
- это шаблон! Как мы это делали с let
, мы можем сопоставить кортеж в аргументах функции с шаблоном. Листинг 18 - 7 разбивает значения в кортеже, когда мы передаем его в функцию.
Filename: src/main.rs
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
Листинг 18 - 7: Функция с параметрами, которые деструктурируют кортеж
Этот код выводит Current location: (3, 5)
. Значения &(3, 5)
соответствуют шаблону &(x, y)
, поэтому x
равно значению 3
, а y
равно значению 5
.
Мы также можем использовать шаблоны в списках параметров замыканий так же, как и в списках параметров функций, потому что замыкания похожи на функции, как обсуждалось в главе 13.
На этом этапе вы узнали несколько способов использования шаблонов, но шаблоны работают по - другому в каждом месте, где мы можем их использовать. В некоторых местах шаблоны должны быть неотрицательными; в других обстоятельствах они могут быть отрицательными. Мы обсудим эти два понятия далее.
Поздравляем! Вы завершили лабораторную работу "Все места, где можно использовать шаблоны". Вы можете выполнить больше лабораторных работ в LabEx, чтобы улучшить свои навыки.