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

RustRustBeginner
Практиковаться сейчас

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

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

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

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

Похожие по конструкции на 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, является шаблоном. Например, в 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', выводя первую строку результата.

let - выражения

До этой главы мы только явно обсуждали использование шаблонов с 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, чтобы улучшить свои навыки.