Enums

Перечисления

Перечисление — это тип данных, который позволяет определить набор именованных значений (вариантов). Каждый вариант может быть просто именем или содержать дополнительные данные.

enum Money {  
    Rub,  
    Kop  
}

Здесь мы определили перечисление Money с двумя вариантами: Rub, и Kop. Эти варианты не содержат дополнительных данных — они просто имена, которые представляют возможные состояния. В терминах Rust такие варианты без данных часто называют “unit-like” (похожими на Unit), но это не совсем то же самое, что массив или указатели.

Unit

“Unit” в Rust — это специальный тип (), который имеет только одно значение, тоже обозначаемое как (). Это что-то вроде “пустого значения”, которое часто используется, когда функция ничего не возвращает. Например:

fn do_nothing() {
    // Ничего не возвращаем, implicitly возвращается ()
}

В случае с Money каждый вариант (Rub и Kop) сам по себе не является типом (), но его можно рассматривать как “unit-like”, потому что он не несёт дополнительных данных. Это просто маркер, который говорит: “Я одно из двух состояний”.

Enum в памяти

Внутри памяти Money представлен как небольшое целое число (обычно 1 байт для простых перечислений вроде этого), называемое “дискриминантом”. Этот дискриминант указывает, какой вариант сейчас используется:

  • Money::Rub → 0
  • Money::Kop → 1 Но это внутренняя реализация. Для программиста это просто разные состояния.

match

Аналог switch в других языках, однако, круче: его проверка не сводится к bool, а также реакция на каждое действие может быть блоком:

fn main() {
    let number = 42;
    match number {
        n if n > 0 => println!("Положительное: {}", x),
        n if n < 0 => println!("Отрицательное: {}", y),
        _ => println!("Ноль"), } }

n - привязка (binding). Когда ты пишешь n if n > 0, то говоришь: “возьми значение number и назови его n для этой ветки. Затем проверь условие if n > 0. Если оно истинно, выполни действие”. Обычно match используется с точными значениями (например, 1 => ...2 => ...), но добавление if позволяет проверять более сложные условия, как в этом примере (положительное, отрицательное или ноль). Это называется guards (охрана) в Rust.

Ещё пример:

fn main() {  
    let m = Money::Kop;  
    println!("Я нашёл кошелёк, а там {}p",match_value_in_kop(m));  
}  
  
fn match_value_in_kop(money: Money) -> u8 {  
    match money {  
        Money::Rub => 100,  
        Money::Kop => {  
            println!("Счастливая копейка!");  
            1  
        }  }  }

match как выражение

fn main() {
    let number = 3;
    let result = match number {
        1 => "один",
        2 => "два",
        _ => "другое",
    };
    println!("Результат: {}", result); // Вывод: Результат: другое
}

Проверка условия и запуск соответствующего метода:

struct State {
    color: (u8, u8, u8),
    position: Point,
    quit: bool,
}

impl State {
    fn change_color(&mut self, color: (u8, u8, u8)) {
        self.color = color; }

    fn quit(&mut self) {
        self.quit = true; }

    fn echo(&self, s: String) {
        println!("{}", s); }

    fn move_position(&mut self, p: Point) {
        self.position = p; }

    fn process(&mut self, message: Message) {
        match message { // проверка и запуск одного из методов
            Message::Quit => self.quit(),
            Message::Echo(x) => self.echo(x),
            Message::ChangeColor(x, y, z) => self.change_color((x, y, z)),
            Message::Move(x) => self.move_position(x),
        } } }

Использование Option как enum

fn divide(a: i32, b: i32) -> Option {
    if b == 0 {
        None
    } else {
        Some(a / b)
    }}

fn main() {
    match divide(10, 2) {
        Some(result) => println!("Result: {}", result), // Result: 5
        None => println!("Division by zero!"),
    }}

if let

В случае, когда выбор сводится к тому, что мы сравниваем 1 вариант с заданным паттерном и далее запускаем код при успехе, а в случае неравенства ничего не делаем, можно вместо match применять более короткую конструкцию if-let:

    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);
    }

Применение if-let - это синтаксический сахар, укорачивает код, однако, лишает нас проверки наличия обработчиков на все варианты возвращаемых значений как в конструкции match.

Сравнение величин

Для сравнения значений в переменных есть метод std::cmp, который возвращает объект типа enum Ordering с вариантами:

use std::cmp::Ordering;
use std:io;

fn main() {
let secret_number = 42;
let mut guess = String::new();
io::stdin()
.read_line(&guess)
.expect("Read line failed!");
  
let guess :i32 = guess.trim().parse()
  .expect("Error! Non-number value entered.");

match guess.cmp(&secret_number) {
  Ordering::Greater => println!("Number too big"),
  Ordering::Less => println!("Number too small"),
  Ordering::Equal => println("Found exact number!")
 }
}