if let

В некоторых случаях использование match выглядит неуклюже. Например:


#![allow(unused_variables)]
fn main() {
// Создаём переменную `optional` типа `Option<i32>`
let optional = Some(7);

match optional {
    Some(i) => {
        println!("Это очень большая строка и `{:?}`", i);
        // ^ Нужно 2 отступа только для того, чтобы извлечь `i`
    },
    _ => {},
    // ^ Обязателен, так как `match` исчерпывающий. Не выглядит ли это
    // как потерянное пространство?
};

}

if let намного компактнее и выразительнее для данного случая и, кроме того, позволяет рассмотреть различные варианты ошибок.

fn main() {
    // Все переменные типа `Option<i32>`
    let number = Some(7);
    let letter: Option<i32> = None;
    let emoticon: Option<i32> = None;

    // Конструкция `if let` читает, как: "Если `let` деструктуризирует `number` в
    // `Some(i)`, выполнить блок (`{}`).
    if let Some(i) = number {
        println!("Соответствует {:?}!", i);
    }

    // Если нужно указать, что делать, в случае ошибки, можно добавить else:
    if let Some(i) = letter {
        println!("Соответствует {:?}!", i);
    } else {
        // Ошибка деструктуризации. Переходим к обработке ошибки.
        println!("Не соответствует числу. Давайте попробуем строку!");
    }

    // Добавляем ещё одну ситуацию несоответствия образцу.
    let i_like_letters = false;

    if let Some(i) = emoticon {
        println!("Соответствует {:?}!", i);
    // Оцените условие `else if`, чтобы увидеть,
    // должна ли быть альтернативная ветка отказа:
    } else if i_like_letters {
        println!("Не соответствует числу. Давайте попробуем строку!");
    } else {
        // Рассматриваем ложное условие. Эта ветвь по умолчанию:
        println!("Мне не нравится сравнивать строки. Давайте возьмём смайлик :)!");
    }
}

Точно так же, if let может быть использован для сравнения любого значения перечисления:

// Наш пример перечисления
enum Foo {
    Bar,
    Baz,
    Qux(u32)
}

fn main() {
    // Создание переменных примера
    let a = Foo::Bar;
    let b = Foo::Baz;
    let c = Foo::Qux(100);
    
    // Переменная `a` соответствует `Foo::Bar`
    if let Foo::Bar = a {
        println!("a = Foo::Bar");
    }
    
    // Переменная `b` не соответствует `Foo::Bar`.
    // Поэтому ничего не выведется на экран
    if let Foo::Bar = b {
        println!("b = Foo::Bar");
    }
    
    // Переменная `c` соответствует `Foo::Qux`, которая имеет значение
    // аналогичное `Some()` как в предыдущем примере:
    if let Foo::Qux(value) = c {
        println!("c ={}", value);
    }

    // С `if let` также работает и привязка
    if let Foo::Qux(value @ 100) = c {
        println!("c = 100");
    }
}

Другое преимущество if let в том, что он позволяет сопоставлять нам не параметризованные варианты перечисления. Это возможно даже если для перечисления не реализован и не выведен типаж PartialEq. В некоторых случаях, if Foo::Bar == a не скомпилируется, потому что экземпляры перечисления не могут быть равны. Однако, с if let всё будет работать.

Хотите вызов? Исправьте следующий пример с использованием if let :

// Для это перечисление намеренно не добавлен #[derive(PartialEq)],
// и мы не реализовывали для него PartialEq. Вот почему сравнение Foo::Bar == a терпит неудачу.
enum Foo {Bar}

fn main() {
    let a = Foo::Bar;

    // Переменная соответствует Foo::Bar
    if Foo::Bar == a {
    // ^-- это вызовет ошибку компиляции. Используйте `if let` вместо этого.
        println!("a is foobar");
    }
}

Смотрите также:

enum, Option, и RFC