Лаконічний контроль виконання конструкцією 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),
        _ => (),
    }
}

Listing 6-6: A match that only cares about executing code when the value is Some

Якщо значення є 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, де вираз стоїть після match, а шаблон є його першим рукавом. У цьому випадку шаблоном буде Some(max), і max зв'язується зі значенням всередині Some. Тепер ми можемо використати max у тілі блоку if let так само, як ми використали max у відповідному рукаві match. Код у блоці if let не буде виконано, якщо значення не відповідає шаблону.

Використання if let означає, що вам треба менше друкувати, менше ставити відступів і писати менше зайвого коду. Разом з тим, ми втрачаємо перевірку на вичерпність, до якої зобов'язує match. Вибір між match та if let залежить від того, що ви робите у конкретній ситуації та чи лаконічність варта втрати перевірки на вичерпність.

In other words, you can think of if let as syntax sugar for a match that runs code when the value matches one pattern and then ignores all other values.

У if let можна також додати else. Блок, що іде після 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;
    }
}

If you have a situation in which your program has logic that is too verbose to express using a match, remember that if let is in your Rust toolbox as well.

Підсумок

Ми щойно розібрали, як використовувати енуми для створення власних типів, які можуть набувати одне з множини перелічених значень. Ми показали, як тип Option<T> зі стандартної бібліотеки допомагає використовувати систему типів для уникання помилок. Коли значення енума мають дані всередині, можна скористатися match чи if let, щоб витягти та використати ці значення, залежно від того, скільки різних варіантів вам треба обробити.

Ваші програми Rust тепер можуть виражати концепції з проблемної області за допомогою структур та енумів. Створення власних типів для використання у вашому API гарантує безпеку типів: компілятор забезпечить, що ваші функції отримають лише значення тих типів, на які ці функції очікують.

In order to provide a well-organized API to your users that is straightforward to use and only exposes exactly what your users will need, let’s now turn to Rust’s modules.