Перечисления
Syntax
Enumeration →
enum IDENTIFIER GenericParams? WhereClause? { EnumVariants? }
EnumVariants → EnumVariant ( , EnumVariant )* ,?
EnumVariant →
OuterAttribute* Visibility?
IDENTIFIER ( EnumVariantTuple | EnumVariantStruct )? EnumVariantDiscriminant?
EnumVariantTuple → ( TupleFields? )
EnumVariantStruct → { StructFields? }
Перечисление, также называемое 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. Однако компилятор может использовать меньший тип (или
другие средства различения вариантов) в своём фактическом расположении в памяти.
Присваивание значений дискриминантов
Явные дискриминанты
В двух случаях дискриминант варианта может быть явно установлен
после имени варианта с помощью = и константного выражения:
- если перечисление является “только-единичным”.
-
если используется примитивное представление. Например:
#![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 } } }