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

Перечисления задают тип через набор вариантов; на их основе построен Option — замена null.

Перечисление (enum) — тип, значение которого — ровно один из заранее перечисленных вариантов, причём каждый вариант может нести свои данные.

Простые перечисления

Enum описывает значение, которое может быть одним из нескольких вариантов. Это надёжнее строковых констант: компилятор знает полный список и не даст забыть ни один.

enum Direction {
    North,
    South,
    East,
    West,
}

fn describe(d: Direction) -> &'static str {
    match d {
        Direction::North => "север",
        Direction::South => "юг",
        Direction::East => "восток",
        Direction::West => "запад",
    }
}

fn main() {
    println!("{}", describe(Direction::East));
}

Вывод:

восток

Варианты с данными

Сила Rust-перечислений в том, что варианты могут нести данные, и у разных вариантов — разные. Один тип Message описывает совершенно разные по форме сообщения.

enum Message {
    Quit,                       // без данных
    Move { x: i32, y: i32 },    // именованные поля
    Write(String),              // одно значение
    Color(u8, u8, u8),          // три значения
}

fn handle(m: Message) {
    match m {
        Message::Quit => println!("выход"),
        Message::Move { x, y } => println!("шаг в ({x}, {y})"),
        Message::Write(text) => println!("текст: {text}"),
        Message::Color(r, g, b) => println!("цвет {r},{g},{b}"),
    }
}

fn main() {
    handle(Message::Move { x: 3, y: 7 });
    handle(Message::Write(String::from("привет")));
}

Вывод:

шаг в (3, 7)
текст: привет

Option — Rust без null

В большинстве языков любое значение может оказаться null, и забытая проверка приводит к падению (NullPointerException и его родня). Создатель null назвал это своей «ошибкой на миллиард долларов». В Rust нет null вообще. Вместо него — перечисление Option<T> из стандартной библиотеки:

// так Option определён в стандартной библиотеке
enum Option<T> {
    Some(T), // значение есть, вот оно
    None,    // значения нет
}

Хитрость в том, что отсутствие значения теперь видно в типе. Если функция может ничего не вернуть, её тип — Option<T>, и компилятор заставит вас обработать вариант None. Забыть проверку невозможно.

fn find_even(nums: &[i32]) -> Option<i32> {
    for &n in nums {
        if n % 2 == 0 {
            return Some(n); // нашли — возвращаем Some
        }
    }
    None // не нашли
}

fn main() {
    let data = [1, 3, 4, 7];
    match find_even(&data) {
        Some(n) => println!("первое чётное: {n}"),
        None => println!("чётных нет"),
    }
}

Вывод:

первое чётное: 4

Чтобы достать значение из Option, его нужно «распаковать» — через match, if let или удобные методы. Это значит, что обращение к отсутствующему значению просто не пройдёт компиляцию. Подробно с Option и match работаем в следующем разделе.

Итог

  • Enum — тип «один из вариантов»; варианты могут нести разные данные.
  • Option<T> заменяет null: Some(значение) или None.
  • Отсутствие значения видно в типе, поэтому компилятор не даёт забыть его обработать.
Проверьте себя
1. Что особенного в вариантах перечислений Rust по сравнению с константами-числами?
AНичего
BКаждый вариант может нести свои данные, причём разной формы
CОни всегда числа
DИх не больше двух
2. Чем Option<T> заменяет null?
AЭто другое имя для null
BОтсутствие значения выражается вариантом None, и его видно в типе
COption всегда содержит значение
DOption — это исключение
3. Почему в Rust нельзя случайно обратиться к отсутствующему значению?
ARust не проверяет это
BЧтобы достать значение из Option, его нужно распаковать, иначе код не скомпилируется
CВсе значения всегда есть
DЭто проверяется только в рантайме
Поддержать проект