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

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

fn main() { enum Message { Quit, ChangeColor(i32, i32, i32), Move { x: i32, y: i32 }, Write(String), } }
enum Message {
    Quit,
    ChangeColor(i32, i32, i32),
    Move { x: i32, y: i32 },
    Write(String),
}

Синтаксис для объявления вариантов схож с синтаксисом для объявления структур: у вас могут быть варианты без данных (как unit-подобные структуры), варианты с именованными данными и варианты с безымянными данными (подобно кортежным структурам). Варианты перечисления имеют один и тот же тип, и в отличии от структур не являются определением отдельных типов. Значение перечисления может соответствовать любому из вариантов. Из-за этого перечисления иногда называют тип-сумма (sum-type): множество возможных значений перечисления — это сумма множеств возможных значений каждого варианта.

Мы используем синтаксис :: чтобы использовать имя каждого из вариантов. Их область видимости ограничена именем самого перечисления. Это позволяет использовать оба варианта из примера ниже совместно:

fn main() { enum Message { Move { x: i32, y: i32 }, } let x: Message = Message::Move { x: 3, y: 4 }; enum BoardGameTurn { Move { squares: i32 }, Pass, } let y: BoardGameTurn = BoardGameTurn::Move { squares: 1 }; }
let x: Message = Message::Move { x: 3, y: 4 };

enum BoardGameTurn {
    Move { squares: i32 },
    Pass,
}

let y: BoardGameTurn = BoardGameTurn::Move { squares: 1 };

Оба варианта имеют одинаковое имя Move, но поскольку область видимости каждого из них ограничена именем соответствующего перечисления, они могут быть использованы без конфликтов.

Значение перечисления, в дополнение к любым данным, которые связаны с ним, содержит информацию о том, какой именно это вариант. Это иногда называют размеченное объединение (tagged union), поскольку данные включают в себя метку, обозначающую что это за тип.

fn main() { fn process_color_change(msg: Message) { let Message::ChangeColor(r, g, b) = msg; // ошибка времени компиляции } }
fn process_color_change(msg: Message) {
    let Message::ChangeColor(r, g, b) = msg; // ошибка времени компиляции
}

То, что пользовательские типы по умолчанию не поддерживают операции, может показаться довольно ограниченным. Но это ограничение, которое мы всегда можем преодолеть. Есть два способа: реализовать операцию самостоятельно, или воспользоваться сопоставлением с образцом с помощью match, о котором вы узнаете в следующем разделе. Пока мы еще недостаточно знаем Rust, чтобы реализовывать операции, но мы научимся делать это в разделе traits.

Конструкторы как функции

Конструктор перечисления может быть также использован как обычная функция. Например:

fn main() { enum Message { Write(String), } let m = Message::Write("Hello, world".to_string()); }
let m = Message::Write("Hello, world".to_string());

тоже самое, что и

fn main() { enum Message { Write(String), } fn foo(x: String) -> Message { Message::Write(x) } let x = foo("Hello, world".to_string()); }
fn foo(x: String) -> Message {
    Message::Write(x)
}

let x = foo("Hello, world".to_string());

На данный момент это не так уж и полезно для нас, но когда мы перейдем к замыканиям, мы поговорим о передаче функций в роли аргумента другой функции. Например, с помощью итераторов мы можем преобразовывать вектор строк в вектор состоящий из Message::Write:

fn main() { enum Message { Write(String), } let v = vec!["Hello".to_string(), "World".to_string()]; let v1: Vec<Message> = v.into_iter().map(Message::Write).collect(); }

let v = vec!["Hello".to_string(), "World".to_string()];

let v1: Vec<Message> = v.into_iter().map(Message::Write).collect();