Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

Перечисление, также называемое enum, - это одновременное определение именованного типа перечисления, а также набора конструкторов, которые могут быть использованы для создания или сопоставления с образцом значений соответствующего типа перечисления.

Перечисления объявляются с ключевым словом enum.

Объявление enum определяет тип перечисления в пространстве имён типов модуля или блока, где оно находится.

Пример элемента enum и его использования:

#![allow(unused)]
fn main() {
enum Animal {
    Dog,
    Cat,
}

let mut a: Animal = Animal::Dog;
a = Animal::Cat;
}

Конструкторы перечислений могут иметь именованные или неименованные поля:

#![allow(unused)]
fn main() {
enum Animal {
    Dog(String, f64),
    Cat { name: String, weight: f64 },
}

let mut a: Animal = Animal::Dog("Cocoa".to_string(), 37.2);
a = Animal::Cat { name: "Spotty".to_string(), weight: 2.7 };
}

В этом примере Cat является вариантом перечисления, похожим на структуру, тогда как Dog просто называется вариантом перечисления.

Перечисление, в котором ни один конструктор не содержит полей, называется бесполым перечислением. Например, это бесполое перечисление:

#![allow(unused)]
fn main() {
enum Fieldless {
    Tuple(),
    Struct{},
    Unit,
}
}

Если бесполое перечисление содержит только единичные варианты, перечисление называется только-единичным перечислением. Например:

#![allow(unused)]
fn main() {
enum Enum {
    Foo = 3,
    Bar = 2,
    Baz = 1,
}
}

Конструкторы вариантов похожи на определения структур и могут быть доступны по пути от имени перечисления, включая объявления use.

Каждый вариант определяет свой тип в пространстве имён типов, хотя этот тип не может использоваться как спецификатор типа. Кортежные и единичные варианты также определяют конструктор в пространстве имён значений.

Вариант, похожий на структуру, может быть создан с помощью выражения структуры.

Кортежный вариант может быть создан с помощью вызовного выражения или выражения структуры.

Единичный вариант может быть создан с помощью выражения пути или выражения структуры. Например:

#![allow(unused)]
fn main() {
enum Examples {
    UnitLike,
    TupleLike(i32),
    StructLike { value: i32 },
}

use Examples::*; // Создаёт псевдонимы для всех вариантов.
let x = UnitLike; // Выражение пути константного элемента.
let x = UnitLike {}; // Выражение структуры.
let y = TupleLike(123); // Вызовное выражение.
let y = TupleLike { 0: 123 }; // Выражение структуры с использованием целочисленных имён полей.
let z = StructLike { value: 123 }; // Выражение структуры.
}

Дискриминанты

Каждый экземпляр перечисления имеет дискриминант: целое число, логически связанное с ним, которое используется для определения того, какой вариант он содержит.

При представлении Rust дискриминант интерпретируется как значение isize. Однако компилятор может использовать меньший тип (или другие средства различения вариантов) в своём фактическом расположении в памяти.

Присваивание значений дискриминантов

Явные дискриминанты

В двух случаях дискриминант варианта может быть явно установлен после имени варианта с помощью = и константного выражения:

  1. если перечисление является “только-единичным”.
  1. если используется примитивное представление. Например:

    #![allow(unused)]
    fn main() {
    #[repr(u8)]
    enum Enum {
        Unit = 3,
        Tuple(u16),
        Struct {
            a: u8,
            b: u16,
        } = 1,
    }
    }

Неявные дискриминанты

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

#![allow(unused)]
fn main() {
enum Foo {
    Bar,            // 0
    Baz = 123,      // 123
    Quux,           // 124
}

let baz_discriminant = Foo::Baz as u32;
assert_eq!(baz_discriminant, 123);
}

Ограничения

Ошибкой является случай, когда два варианта имеют одинаковый дискриминант.

#![allow(unused)]
fn main() {
enum SharedDiscriminantError {
    SharedA = 1,
    SharedB = 1
}

enum SharedDiscriminantError2 {
    Zero,       // 0
    One,        // 1
    OneToo = 1  // 1 (коллизия с предыдущим!)
}
}

Также ошибкой является наличие неопределённого дискриминанта, когда предыдущий дискриминант является максимальным значением для размера дискриминанта.

#![allow(unused)]
fn main() {
#[repr(u8)]
enum OverflowingDiscriminantError {
    Max = 255,
    MaxPlusOne // Был бы 256, но это переполняет перечисление.
}

#[repr(u8)]
enum OverflowingDiscriminantError2 {
    MaxMinusOne = 254, // 254
    Max,               // 255
    MaxPlusOne         // Был бы 256, но это переполняет перечисление.
}
}

Доступ к дискриминанту

Через mem::discriminant

std::mem::discriminant возвращает непрозрачную ссылку на дискриминант значения перечисления, которую можно сравнивать. Это нельзя использовать для получения значения дискриминанта.

Приведение

Если перечисление является только-единичным (без кортежных и структурных вариантов), то его дискриминант может быть напрямую доступен с помощью числового приведения; например:

#![allow(unused)]
fn main() {
enum Enum {
    Foo,
    Bar,
    Baz,
}

assert_eq!(0, Enum::Foo as isize);
assert_eq!(1, Enum::Bar as isize);
assert_eq!(2, Enum::Baz as isize);
}

Бесполые перечисления могут быть приведены, если они не имеют явных дискриминантов или где только единичные варианты являются явными.

#![allow(unused)]
fn main() {
enum Fieldless {
    Tuple(),
    Struct{},
    Unit,
}

assert_eq!(0, Fieldless::Tuple() as isize);
assert_eq!(1, Fieldless::Struct{} as isize);
assert_eq!(2, Fieldless::Unit as isize);

#[repr(u8)]
enum FieldlessWithDiscriminants {
    First = 10,
    Tuple(),
    Second = 20,
    Struct{},
    Unit,
}

assert_eq!(10, FieldlessWithDiscriminants::First as u8);
assert_eq!(11, FieldlessWithDiscriminants::Tuple() as u8);
assert_eq!(20, FieldlessWithDiscriminants::Second as u8);
assert_eq!(21, FieldlessWithDiscriminants::Struct{} as u8);
assert_eq!(22, FieldlessWithDiscriminants::Unit as u8);
}

Приведение указателей

Если перечисление указывает примитивное представление, то дискриминант может быть надёжно доступен через небезопасное приведение указателей:

#![allow(unused)]
fn main() {
#[repr(u8)]
enum Enum {
    Unit,
    Tuple(bool),
    Struct{a: bool},
}

impl Enum {
    fn discriminant(&self) -> u8 {
        unsafe { *(self as *const Self as *const u8) }
    }
}

let unit_like = Enum::Unit;
let tuple_like = Enum::Tuple(true);
let struct_like = Enum::Struct{a: false};

assert_eq!(0, unit_like.discriminant());
assert_eq!(1, tuple_like.discriminant());
assert_eq!(2, struct_like.discriminant());
}

Перечисления без вариантов

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

#![allow(unused)]
fn main() {
enum ZeroVariants {}
}

Перечисления с нулевым количеством вариантов эквивалентны типу never, но они не могут быть приведены к другим типам.

#![allow(unused)]
fn main() {
enum ZeroVariants {}
let x: ZeroVariants = panic!();
let y: u32 = x; // ошибка несоответствия типов
}

Видимость вариантов

Варианты перечислений синтаксически допускают аннотацию Видимости, но это отклоняется при проверке перечисления. Это позволяет разбирать элементы с унифицированным синтаксисом в различных контекстах, где они используются.

#![allow(unused)]
fn main() {
macro_rules! mac_variant {
    ($vis:vis $name:ident) => {
        enum $name {
            $vis Unit,

            $vis Tuple(u8, u16),

            $vis Struct { f: u8 },
        }
    }
}

// Пустой `vis` разрешён.
mac_variant! { E }

// Это разрешено, поскольку удаляется до проверки.
#[cfg(false)]
enum E {
    pub U,
    pub(crate) T(u8),
    pub(super) T { f: String }
}
}