Компактное управление потоком выполнения с if let
Синтаксис if let
позволяет скомбинировать if
и let
в менее многословную конструкцию, и затем обработать значения соответствующе только одному шаблону, одновременно игнорируя все остальные. Рассмотрим программу в листинге 6-6, которая обрабатывает сопоставление значения Option<u8>
в переменной config_max
, но хочет выполнить код только в том случае, если значение является вариантом Some
.
fn main() { let config_max = Some(3u8); match config_max { Some(max) => println!("The maximum is configured to be {max}"), _ => (), } }
Если значение равно Some
, мы распечатываем значение в варианте Some
, привязывая значение к переменной max
в шаблоне. Мы не хотим ничего делать со значением None
. Чтобы удовлетворить выражение match
, мы должны добавить _ => ()
после обработки первой и единственной ветки, и добавление шаблонного кода раздражает.
Вместо этого, мы могли бы написать это более коротким способом, используя if let
. Следующий код ведёт себя так же, как выражение match
в листинге 6-6:
fn main() { let config_max = Some(3u8); if let Some(max) = config_max { println!("The maximum is configured to be {max}"); } }
Синтаксис if let
принимает шаблон и выражение, разделённые знаком равенства. Он работает так же, как match
, когда в него на вход передадут выражение и подходящим шаблоном для этого выражения окажется первая ветка. В данном случае шаблоном является Some(max)
, где max
привязывается к значению внутри Some
. Затем мы можем использовать max
в теле блока if let
так же, как мы использовали max
в соответствующей ветке match
. Код в блоке if let
не запускается, если значение не соответствует шаблону.
Используя if let
мы меньше печатаем, меньше делаем отступов и меньше получаем шаблонного кода. Тем не менее, мы теряем полную проверку всех вариантов, предоставляемую выражением match
. Выбор между match
и if let
зависит от того, что вы делаете в вашем конкретном случае и является ли получение краткости при потере полноты проверки подходящим компромиссом.
Другими словами, вы можете думать о конструкции if let
как о синтаксическом сахаре для match
, который выполнит код если входное значение будет соответствовать единственному шаблону, и проигнорирует все остальные значения.
Можно добавлять else
к if let
. Блок кода, который находится внутри else
аналогичен по смыслу блоку кода ветки связанной с шаблоном _
выражения match
(которое эквивалентно сборной конструкции if let
и else
). Вспомним объявление перечисления Coin
в листинге 6-4, где вариант Quarter
также содержит внутри значение штата типа UsState
. Если бы мы хотели посчитать все монеты не являющиеся четвертями, а для четвертей печатать название штата, то мы могли бы сделать это с помощью выражения match
таким образом:
#[derive(Debug)] enum UsState { Alabama, Alaska, // --snip-- } enum Coin { Penny, Nickel, Dime, Quarter(UsState), } fn main() { let coin = Coin::Penny; let mut count = 0; match coin { Coin::Quarter(state) => println!("State quarter from {state:?}!"), _ => count += 1, } }
Или мы могли бы использовать выражение if let
и else
так:
#[derive(Debug)] enum UsState { Alabama, Alaska, // --snip-- } enum Coin { Penny, Nickel, Dime, Quarter(UsState), } fn main() { let coin = Coin::Penny; let mut count = 0; if let Coin::Quarter(state) = coin { println!("State quarter from {state:?}!"); } else { count += 1; } }
Если у вас есть ситуация в которой ваша программа имеет логику которая слишком многословна для того чтобы её выражать используя match
, помните, о том, что также в вашем наборе инструментов Rust есть if let
.
Итоги
Мы рассмотрели как использовать перечисления для создания пользовательских типов, которые могут быть одним из наборов перечисляемых значений. Мы показали, как тип Option<T>
из стандартной библиотеки помогает использовать систему типов для предотвращения ошибок. А когда значения перечисления имеют данные внутри них, можно использовать match
или if let
, чтобы извлечь и пользоваться значением, в зависимости от того, сколько случаев нужно обработать.
Теперь ваши программы на Rust могут выражать концепции вашей предметной области, используя структуры и перечисления. Создание и использование пользовательских типов в API обеспечивает типобезопасность: компилятор позаботится о том, чтобы функции получали значения только того типа, который они ожидают.
Чтобы предоставить вашим пользователям хорошо организованный API, который прост в использовании и предоставляет только то, что нужно вашим пользователям, надо поговорить о модулях в Rust.