Главная страница
Навигация по странице:

  • Листинг 6-3: Перечисление и выражение match, использующее в качестве шаблонов его варианты

  • Шаблоны, которые привязывают значения

  • Листинг 6-4: Перечисление Coin, в котором вариант Quarter также сохраняет значение

  • Сопоставление шаблона для

  • Листинг 6-5: Функция, использующая выражение match для

  • Match объемлет все варианты значения

  • Универсальные шаблоны и заполнитель

  • Компактное управление потоком выполнения с

  • Листинг 6-6. Выражение match, которое выполнит код только при значении равном

  • Язык программирования Rust


    Скачать 7.02 Mb.
    НазваниеЯзык программирования Rust
    Дата12.04.2023
    Размер7.02 Mb.
    Формат файлаpdf
    Имя файлаThe Rust Programming Language_ru.pdf
    ТипУчебник
    #1056301
    страница13 из 62
    1   ...   9   10   11   12   13   14   15   16   ...   62

    Управляющая конструкция match
    В Rust есть чрезвычайно мощный механизм управления потоком, именуемый match
    ,
    который позволяет сравнивать значение с различными шаблонами и затем выполнять код в зависимости от того, какой из шаблонов совпал. Шаблоны могут состоять из литеральных значений, имён переменных, подстановочных знаков и многого другого; в главе 18 рассматриваются все различные виды шаблонов и то, что они делают. Сила match заключается в выразительности шаблонов и в том, что компилятор проверяет, что все возможные случаи обработаны.
    Думайте о выражении match как о машине для сортировки монет: монеты скользят по дорожке с различными по размеру отверстиями, и каждая монета падает через первое попавшееся отверстие, в которое она поместилась. Таким же образом значения проходят через каждый шаблон в match
    , и при первом же "подходящем" шаблоне значение попадает в соответствующий блок кода, который будет использоваться во время выполнения.
    Говоря о монетах, давайте используем их в качестве примера, используя match
    ! Для этого мы напишем функцию, которая будет получать на вход неизвестную монету
    Соединённых Штатов и, подобно счётной машине, определять, какая это монета, и возвращать её стоимость в центах, как показано в листинге 6-3.
    Листинг 6-3: Перечисление и выражение
    match
    , использующее в качестве шаблонов его варианты
    Давайте разберём match в функции value_in_cents
    . Сначала мы используем ключевое слово match
    , за которым следует выражение, которое в данном случае является значением coin
    . Это кажется очень похожим на выражение, используемое в if
    , но есть большая разница: в if выражение должно возвращать булево значение, а здесь оно может возвращать любой тип. Типом coin в данном примере является перечисление
    Coin
    , которое мы определили в первой строке.
    enum
    Coin
    {
    Penny,
    Nickel,
    Dime,
    Quarter,
    } fn value_in_cents
    (coin: Coin) -> u8
    { match coin {
    Coin::Penny =>
    1
    ,
    Coin::Nickel =>
    5
    ,
    Coin::Dime =>
    10
    ,
    Coin::Quarter =>
    25
    ,
    }
    }

    Далее идут ветки match
    . Ветки состоят из двух частей: шаблон и некоторый код. Здесь первая ветка имеет шаблон, который является значением
    Coin::Penny
    , затем идёт оператор
    =>
    , который разделяет шаблон и код для выполнения. Код в этом случае - это просто значение
    1
    . Каждая ветка отделяется от последующей при помощи запятой.
    Когда выполняется выражение match
    , оно сравнивает полученное значение с образцом каждой ветки по порядку. Если шаблон совпадает со значением, то выполняется код,
    связанный с этим шаблоном. Если этот шаблон не соответствует значению, то выполнение продолжается со следующей ветки, так же, как в автомате по сортировке монет. У нас может быть столько веток, сколько нужно: в листинге 6-3 наш match состоит из четырёх веток.
    Код, связанный с каждой веткой, является выражением, а полученное значение выражения в соответствующей ветке — это значение, которое возвращается для всего выражения match
    Обычно фигурные скобки не используются, если код совпадающей ветви невелик, как в листинге 6-3, где каждая ветвь просто возвращает значение. Если вы хотите выполнить несколько строк кода в одной ветви, вы должны использовать фигурные скобки, а запятая после этой ветви необязательна. Например, следующий код печатает "Lucky penny!" каждый раз, когда метод вызывается с
    Coin::Penny
    , но при этом он возвращает последнее значение блока -
    1
    :
    Шаблоны, которые привязывают значения
    Есть ещё одно полезное качество у веток в выражении match
    : они могут привязываться к частям тех значений, которые совпали с шаблоном. Благодаря этому можно извлекать значения из вариантов перечисления.
    В качестве примера, давайте изменим один из вариантов перечисления так, чтобы он хранил в себе данные. С 1999 по 2008 год Соединённые Штаты чеканили 25 центов с различным дизайном на одной стороне для каждого из 50 штатов. Ни одна другая монета не получила дизайна штата, только четверть доллара имела эту дополнительную особенность. Мы можем добавить эту информацию в наш enum путём изменения варианта
    Quarter и включить в него значение
    UsState
    , как сделано в листинге 6-4.
    fn value_in_cents
    (coin: Coin) -> u8
    { match coin {
    Coin::Penny => { println!
    (
    "Lucky penny!"
    );
    1
    }
    Coin::Nickel =>
    5
    ,
    Coin::Dime =>
    10
    ,
    Coin::Quarter =>
    25
    ,
    }
    }

    Листинг 6-4: Перечисление
    Coin
    , в котором вариант
    Quarter
    также сохраняет значение
    UsState
    Представьте, что ваш друг пытается собрать четвертаки всех 50 штатов. Сортируя монеты по типу, мы также будем сообщать название штата, к которому относится каждый четвертак, чтобы, если у нашего друга нет такой монеты, он мог добавить её в свою коллекцию.
    В выражении match для этого кода мы добавляем переменную с именем state в
    шаблон, который соответствует значениям варианта
    Coin::Quarter
    . Когда
    Coin::Quarter совпадёт с шаблоном, переменная state будет привязана к значению штата этого четвертака. Затем мы сможем использовать state в коде этой ветки, вот так:
    Если мы сделаем вызов функции value_in_cents(Coin::Quarter(UsState::Alaska))
    , то coin будет иметь значение
    Coin::Quarter(UsState::Alaska)
    . Когда мы будем сравнивать это значение с каждой из веток, ни одна из них не будет совпадать, пока мы не достигнем варианта
    Coin::Quarter(state)
    . В этот момент state привяжется к значению
    UsState::Alaska
    . Затем мы сможем использовать эту привязку в выражении println!
    , получив таким образом внутреннее значение варианта
    Quarter перечисления
    Coin
    Сопоставление шаблона для Option
    #[derive(Debug)]
    // so we can inspect the state in a minute enum
    UsState
    {
    Alabama,
    Alaska,
    // --snip--
    } enum
    Coin
    {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
    } fn value_in_cents
    (coin: Coin) -> u8
    { match coin {
    Coin::Penny =>
    1
    ,
    Coin::Nickel =>
    5
    ,
    Coin::Dime =>
    10
    ,
    Coin::Quarter(state) => { println!
    (
    "State quarter from {:?}!"
    , state);
    25
    }
    }
    }

    В предыдущем разделе мы хотели получить внутреннее значение
    T
    для случая
    Some при использовании
    Option
    ; мы можем обработать тип
    Option
    используя match
    ,
    как уже делали с перечислением
    Coin
    ! Вместо сравнения монет мы будем сравнивать варианты
    Option
    , независимо от этого изменения механизм работы выражения match останется прежним.
    Допустим, мы хотим написать функцию, которая принимает
    Option
    и если есть значение внутри, то добавляет 1 к существующему значению. Если значения нет, то функция должна возвращать значение
    None и не пытаться выполнить какие-либо операции.
    Такую функцию довольно легко написать благодаря выражению match
    , код будет выглядеть как в листинге 6-5.
    Листинг 6-5: Функция, использующая выражение
    match
    для
    Option
    Давайте более подробно рассмотрим первое выполнение plus_one
    . Когда мы вызываем plus_one(five)
    , переменная x
    в теле plus_one будет иметь значение
    Some(5)
    . Затем мы сравниваем это значение с каждой ветвью выражения match.
    Значение
    Some(5)
    не соответствует шаблону
    None
    , поэтому мы продолжаем со следующей ветки.
    Совпадает ли
    Some(5)
    с шаблоном
    Some(i)
    ? Да, это так! У нас такой же вариант. Тогда переменная i
    привязывается к значению, содержащемуся внутри
    Some
    , поэтому i
    получает значение
    5
    . Затем выполняется код ассоциированный для данной ветки,
    поэтому мы добавляем 1 к значению i
    и создаём новое значение
    Some со значением
    6
    внутри.
    Теперь давайте рассмотрим второй вызов plus_one в листинге 6-5, где x
    является
    None
    Мы входим в выражение match и сравниваем значение с первой веткой.
    fn plus_one
    (x:
    Option
    <
    i32
    >) ->
    Option
    <
    i32
    > { match x {
    None
    =>
    None
    ,
    Some
    (i) =>
    Some
    (i +
    1
    ),
    }
    } let five =
    Some
    (
    5
    ); let six = plus_one(five); let none = plus_one(
    None
    );
    None
    =>
    None
    ,
    Some
    (i) =>
    Some
    (i +
    1
    ),
    None
    =>
    None
    ,

    Оно совпадает! Для данной ветки шаблон (None) не подразумевает наличие какого-то значения к которому можно было бы что-то добавить, поэтому программа останавливается и возвращает значение которое находится справа от
    =>
    - т.е.
    None
    . Так как шаблон первой ветки совпал, то никакие другие шаблоны веток не сравниваются.
    Комбинирование match и перечислений полезно во многих ситуациях. Вы часто будете видеть подобную комбинацию в коде на Rust: сделать сопоставление значений перечисления используя match
    , привязать переменную к данным внутри значения,
    выполнить код на основе привязанных данных. Сначала это может показаться немного сложным, но как только вы привыкнете, то захотите чтобы такая возможность была бы во всех языках. Это неизменно любимый пользователями приём.
    Match объемлет все варианты значения
    Есть ещё один аспект match
    , который мы должны обсудить: шаблоны должны покрывать все возможные варианты. Рассмотрим эту версию нашей функции plus_one
    , которая содержит ошибку и не компилируется:
    Мы не обработали вариант
    None
    , поэтому этот код вызовет дефект в программе. К
    счастью, Rust знает и умеет ловить такой случай. Если мы попытаемся скомпилировать такой код, мы получим ошибку компиляции:
    fn plus_one
    (x:
    Option
    <
    i32
    >) ->
    Option
    <
    i32
    > { match x {
    Some
    (i) =>
    Some
    (i +
    1
    ),
    }
    }
    $
    cargo run
    Compiling enums v0.1.0 (file:///projects/enums) error[E0004]: non-exhaustive patterns: `None` not covered
    -->
    src/main.rs:3:15
    |
    3 | match x {
    | ^ pattern `None` not covered
    | note: `Option` defined here
    = note: the matched value is of type `Option` help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
    |
    4

    Some(i) => Some(i + 1),
    5 None => todo!(),
    |
    For more information about this error, try `rustc --explain E0004`. error: could not compile `enums` due to previous error

    Rust знает, что мы не описали все возможные случаи, и даже знает, какой именно из шаблонов мы упустили! Сопоставления в Rust являются исчерпывающими: мы должны покрыть все возможные варианты, чтобы код был корректным. Особенно в случае
    Option
    , когда Rust не даёт нам забыть обработать явным образом значение
    None
    ,
    тем самым он защищает нас от предположения, что у нас есть значение, в то время как у нас может быть и null, что делает невозможным совершить ошибку на миллиард долларов, о которой говорилось ранее.
    Универсальные шаблоны и заполнитель _
    Используя перечисления, мы также можем выполнять специальные действия для нескольких определённых значений, а для всех остальных значений выполнять одно действие по умолчанию. Представьте, что мы реализуем игру, в которой при выпадении
    3 игрок не двигается, а получает новую модную шляпу. Если выпадает 7, игрок теряет шляпу. При всех остальных значениях ваш игрок перемещается на столько-то мест на игровом поле. Вот match
    , реализующий эту логику, в котором результат броска костей жёстко закодирован, а не является случайным значением, а вся остальная логика представлена функциями без тел, поскольку их реализация не входит в рамки данного примера:
    Для первых двух веток шаблонами являются литеральные значения 3 и 7. Для последней ветки, которая охватывает все остальные возможные значения, шаблоном является переменная, которую мы решили назвать other
    . Код, выполняемый для другой ветки,
    использует эту переменную, передавая её в функцию move_player.
    Этот код компилируется, даже если мы не перечислили все возможные значения u8
    ,
    потому что последний паттерн будет соответствовать всем значениям, не указанным в конкретном списке. Этот универсальный шаблон удовлетворяет требованию, что соответствие должно быть исчерпывающим. Обратите внимание, что мы должны поместить ветку с универсальным шаблоном последней, потому что шаблоны оцениваются по порядку. Rust предупредит нас, если мы добавим ветки после универсального шаблона, потому что эти последующие ветки никогда не будут выполняться!
    let dice_roll =
    9
    ; match dice_roll {
    3
    => add_fancy_hat(),
    7
    => remove_fancy_hat(), other => move_player(other),
    } fn add_fancy_hat
    () {} fn remove_fancy_hat
    () {} fn move_player
    (num_spaces: u8
    ) {}

    В Rust также есть шаблон, который можно использовать, когда мы не хотим использовать значение в универсальном шаблоне:
    _
    , который является специальным шаблоном, который соответствует любому значению и не привязывается к этому значению. Это говорит Rust, что мы не собираемся использовать это значение, поэтому
    Rust не будет предупреждать нас о неиспользуемой переменной.
    Давайте изменим правила игры так: если выпадает что-то, кроме 3 или 7, нужно бросить ещё раз. Нам не нужно использовать значение в этом случае, поэтому мы можем изменить наш код, чтобы использовать
    _
    вместо переменной с именем other
    :
    Этот пример также удовлетворяет требованию исчерпывающей полноты, поскольку мы явно игнорируем все остальные значения в последней ветке; мы ничего не забыли.
    Если мы изменим правила игры ещё раз, чтобы в ваш ход не происходило ничего другого, если вы бросаете не 3 или 7, мы можем выразить это, используя единичное значение (пустой тип кортежа, о котором мы упоминали в разделе "Кортежи"
    ) в качестве кода, который идёт вместе с веткой
    _
    :
    Здесь мы явно говорим Rust, что не собираемся использовать никакое другое значение,
    которое не соответствует шаблонам в предыдущих ветках, и не хотим запускать никакой код в этом случае.
    Подробнее о шаблонах и совпадениях мы поговорим в
    Главе 18
    . Пока же мы перейдём к синтаксису if let
    , который может быть полезен в ситуациях, когда выражение match слишком многословно.
    let dice_roll =
    9
    ; match dice_roll {
    3
    => add_fancy_hat(),
    7
    => remove_fancy_hat(),
    _ => reroll(),
    } fn add_fancy_hat
    () {} fn remove_fancy_hat
    () {} fn reroll
    () {} let dice_roll =
    9
    ; match dice_roll {
    3
    => add_fancy_hat(),
    7
    => remove_fancy_hat(),
    _ => (),
    } fn add_fancy_hat
    () {} fn remove_fancy_hat
    () {}

    Компактное управление потоком выполнения с if let
    Синтаксис if let позволяет скомбинировать if и let в менее многословную конструкцию, и затем обработать значения соответствующе только одному шаблону,
    одновременно игнорируя все остальные. Рассмотрим программу в листинге 6-6, которая обрабатывает сопоставление значения
    Option
    в переменной config_max
    , но хочет выполнить код только в том случае, если значение является вариантом
    Some
    Листинг 6-6. Выражение
    match
    , которое выполнит код только при значении равном
    Some
    Если значение равно
    Some
    , мы распечатываем значение в варианте
    Some
    , привязывая значение к переменной max в шаблоне. Мы не хотим ничего делать со значением
    None
    Чтобы удовлетворить выражение match
    , мы должны добавить
    _ => ()
    после обработки первой и единственной ветки, и добавление шаблонного кода раздражает.
    Вместо этого, мы могли бы написать это более коротким способом, используя if let
    Следующий код ведёт себя так же, как выражение match в листинге 6-6:
    Синтаксис if let принимает шаблон и выражение, разделённые знаком равенства. if let сработает так же, как match
    , когда в него на вход передадут выражение и подходящим шаблоном для этого выражения окажется первая ветка. В нашем случае шаблоном является
    Some(max)
    , где max привязывается к значению внутри
    Some
    . Затем мы можем использовать max в теле блока if let так же, как мы использовали max в
    соответствующей ветке match
    . Код в блоке if let не запускается, если значение не соответствует шаблону.
    Используя if let мы меньше печатаем, меньше делаем отступов и меньше получаем шаблонного кода. Тем не менее, мы теряем полную проверку всех вариантов,
    предоставляемую выражением match
    . Выбор между match и if let зависит от того, что вы делаете в вашем конкретном случае и является ли получение краткости при потере полноты проверки подходящим компромиссом.
    Другими словами, вы можете думать о конструкции if let как о синтаксическом сахаре
    для match
    , который выполнит код если входное значение будет соответствовать единственному шаблону, и проигнорирует все остальные значения.
    let config_max =
    Some
    (
    3u8
    ); match config_max {
    Some
    (max) => println!
    (
    "The maximum is configured to be {}"
    , max),
    _ => (),
    } let config_max =
    Some
    (
    3u8
    ); if let
    Some
    (max) = config_max { println!
    (
    "The maximum is configured to be {}"
    , max);
    }

    Можно добавлять else к if let
    . Блок кода, который находится внутри else аналогичен по смыслу блоку кода ветки связанной с шаблоном
    _
    выражения match
    (которое эквивалентно сборной конструкции if let и else
    ). Вспомним объявление перечисления
    Coin в листинге 6-4, где вариант
    Quarter также содержит внутри значение штата типа
    UsState
    . Если бы мы хотели посчитать все монеты не являющиеся четвертями, а для четвертей печатать название штата, то мы могли бы сделать это с помощью выражения match таким образом:
    Или мы могли бы использовать выражение if let и else так:
    Если у вас есть ситуация в которой ваша программа имеет логику которая слишком многословна для того чтобы её выражать используя match
    , помните, о том, что также в вашем наборе инструментов Rust есть if let
    Итоги
    Мы рассмотрели как использовать перечисления для создания пользовательских типов,
    которые могут быть одним из наборов перечисляемых значений. Мы показали, как тип
    Option
    из стандартной библиотеки помогает использовать систему типов для предотвращения ошибок. А когда значения перечисления имеют данные внутри них,
    можно использовать match или if let
    , чтобы извлечь и пользоваться значением, в зависимости от того, сколько случаев нужно обработать.
    Теперь ваши программы на Rust могут выражать концепции вашей предметной области используя структуры и перечисления. Создание и использование пользовательских типов в API обеспечивает типобезопасность: компилятор позаботится о том, чтобы функции получали значения только того типа, который они ожидают.
    Чтобы предоставить вашим пользователям хорошо организованный API, который прост в использовании и предоставляет только то, что нужно вашим пользователям, надо поговорить о модулях в Rust.
    let mut count =
    0
    ; match coin {
    Coin::Quarter(state) => println!
    (
    "State quarter from {:?}!"
    , state),
    _ => count +=
    1
    ,
    } let mut count =
    0
    ; if let
    Coin::Quarter(state) = coin { println!
    (
    "State quarter from {:?}!"
    , state);
    } else
    { count +=
    1
    ;
    }

    1   ...   9   10   11   12   13   14   15   16   ...   62


    написать администратору сайта