Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Где можно использовать образцы

Образцы встречаются в Rust во многих местах, и вы уже много ими пользовались, сами того не замечая! В этом разделе перечислены все места, где образцы допустимы.

Ветки match

Как говорилось в главе 6, образцы используются в ветках выражений match. Формально выражение match состоит из ключевого слова match, значения для сопоставления и одной или нескольких веток. Каждая ветка — это образец и выражение, которое выполняется, если значение совпало с образцом ветки:

match ЗНАЧЕНИЕ {
    ОБРАЗЕЦ => ВЫРАЖЕНИЕ,
    ОБРАЗЕЦ => ВЫРАЖЕНИЕ,
    ОБРАЗЕЦ => ВЫРАЖЕНИЕ,
}

Например, вот выражение match из листинга 6-5, которое сопоставляет значение Option<i32> в переменной x:

match x {
    None => None,
    Some(i) => Some(i + 1),
}

Образцы в этом выражении match — None и Some(i) слева от каждой стрелки.

Одно из требований к выражениям match — они должны быть исчерпывающими: все возможные значения в выражении match должны быть учтены. Один из способов гарантировать, что вы покрыли все случаи, — сделать последнюю ветку «для всего остального». Например, образец из одного имени переменной совпадает с любым значением, поэтому такая ветка покрывает все оставшиеся случаи.

Особый образец _ совпадает с чем угодно, но никогда не привязывается к переменной, поэтому его часто используют в последней ветке. Образец _ удобен, например, когда нужно проигнорировать все значения, не указанные явно. Подробнее об образце _ — в разделе «Игнорирование значений в образце» дальше в этой главе.

Инструкции let

До этой главы мы явно говорили об образцах только в связи с match и if let, но на самом деле пользовались ими и в других местах — в том числе в инструкциях let. Возьмём, например, простое присваивание с let:

#![allow(unused)]
fn main() {
let x = 5;
}

Каждый раз, когда вы писали подобную инструкцию let, вы пользовались образцами, даже если не догадывались об этом! Формально инструкция let выглядит так:

let ОБРАЗЕЦ = ВЫРАЖЕНИЕ;

В инструкциях вроде let x = 5;, где на месте ОБРАЗЦА стоит имя переменной, имя переменной — просто очень простая разновидность образца. Rust сравнивает выражение с образцом и привязывает все найденные имена. Так что в примере let x = 5; x — это образец, который означает «привязать то, что здесь совпадёт, к переменной x». Раз имя x — это весь образец целиком, он, по сути, означает «привязать всё к переменной x, каким бы ни было значение».

Чтобы яснее увидеть, что в let происходит сопоставление с образцом, посмотрите на листинг 19-1, где образец в let разбирает кортеж.

fn main() {
    let (x, y, z) = (1, 2, 3);
}
Листинг 19-1: Разбираем кортеж с помощью образца и создаём сразу три переменные

Здесь кортеж сопоставляется с образцом. Rust сравнивает значение (1, 2, 3) с образцом (x, y, z) и видит, что значение совпадает с образцом — количество элементов в них одинаковое, — поэтому привязывает 1 к x, 2 к y и 3 к z. Этот образец кортежа можно считать тремя вложенными в него образцами отдельных переменных.

Если количество элементов в образце не совпадает с количеством элементов кортежа, типы целиком не совпадут, и будет ошибка компиляции. Например, в листинге 19-2 попытка разобрать кортеж из трёх элементов на две переменные — так не получится.

fn main() {
    let (x, y) = (1, 2, 3);
}
Листинг 19-2: Неправильно построенный образец: число переменных не совпадает с числом элементов кортежа

Попытка скомпилировать этот код даёт ошибку типов:

$ 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 выбирает цвет фона, проверяя несколько условий подряд. Для этого примера мы создали переменные с вписанными в код значениями — в настоящей программе они могли бы приходить из пользовательского ввода.

Файл: src/main.rs
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");
    }
}
Листинг 19-3: Сочетаем 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

Условный цикл while let, устроенный похоже на if 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}");
    }
}
Листинг 19-4: Цикл 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 разобрать на части (деструктурировать) кортеж.

fn main() {
    let v = vec!['a', 'b', 'c'];

    for (index, value) in v.iter().enumerate() {
        println!("{value} is at index {index}");
    }
}
Листинг 19-5: Образец в цикле 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) {
    // здесь код
}

fn main() {}
Листинг 19-6: Сигнатура функции с образцами в параметрах

Часть x — это образец! Как и с let, кортеж в аргументах функции можно сопоставить с образцом. В листинге 19-7 значения кортежа разделяются прямо при передаче его в функцию.

Файл: src/main.rs
fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({x}, {y})");
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}
Листинг 19-7: Функция с параметрами, которые разбирают кортеж

Этот код выводит Current location: (3, 5). Значения &(3, 5) совпадают с образцом &(x, y), так что x — это значение 3, а y — 5.

Образцы можно использовать и в списках параметров замыканий — так же, как в списках параметров функций, ведь замыкания похожи на функции, как мы говорили в главе 13.

Итак, вы увидели несколько способов использовать образцы, но работают образцы не везде одинаково. В одних местах образцы обязаны быть неопровержимыми, в других могут быть опровержимыми. Об этих двух понятиях и пойдёт речь дальше.