diff --git a/src/const_eval.md b/src/const_eval.md index c0560376c..34e34d703 100644 --- a/src/const_eval.md +++ b/src/const_eval.md @@ -113,7 +113,7 @@ Conversely, the following are possible in a const function, but not in a const c [Const parameters]: items/generics.md [dereference operator]: expressions/operator-expr.md#the-dereference-operator [destructors]: destructors.md -[enum discriminants]: items/enumerations.md#custom-discriminant-values-for-fieldless-enumerations +[enum discriminants]: items/enumerations.md#discriminants [expression statements]: statements.md#expression-statements [expressions]: expressions.md [field]: expressions/field-expr.md diff --git a/src/expressions/operator-expr.md b/src/expressions/operator-expr.md index d8658d647..9f7e8edac 100644 --- a/src/expressions/operator-expr.md +++ b/src/expressions/operator-expr.md @@ -368,7 +368,7 @@ reference types and `mut` or `const` in pointer types. | Type of `e` | `U` | Cast performed by `e as U` | |-----------------------|-----------------------|----------------------------------| | Integer or Float type | Integer or Float type | Numeric cast | -| C-like enum | Integer type | Enum cast | +| Enumeration | Integer type | Enum cast | | `bool` or `char` | Integer type | Primitive to integer cast | | `u8` | `char` | `u8` to `char` cast | | `*T` | `*V` where `V: Sized` \* | Pointer to pointer cast | @@ -430,6 +430,10 @@ halfway between two floating point numbers. #### Enum cast Casts an enum to its discriminant, then uses a numeric cast if needed. +Casting is limited to the following kinds of enumerations: + +* [Unit-only enums] +* [Field-less enums] without [explicit discriminants], or where only unit-variants have explicit discriminants #### Primitive to integer cast @@ -632,6 +636,8 @@ See [this test] for an example of using this dependency. [copies or moves]: ../expressions.md#moved-and-copied-types [dropping]: ../destructors.md +[explicit discriminants]: ../items/enumerations.md#explicit-discriminants +[field-less enums]: ../items/enumerations.md#field-less-enum [grouped expression]: grouped-expr.md [literal expression]: literal-expr.md#integer-literal-expressions [logical and]: ../types/boolean.md#logical-and @@ -643,6 +649,7 @@ See [this test] for an example of using this dependency. [assignee expression]: ../expressions.md#place-expressions-and-value-expressions [undefined behavior]: ../behavior-considered-undefined.md [unit]: ../types/tuple.md +[Unit-only enums]: ../items/enumerations.md#unit-only-enum [value expression]: ../expressions.md#place-expressions-and-value-expressions [temporary value]: ../expressions.md#temporaries [this test]: https://github.com/rust-lang/rust/blob/1.58.0/src/test/ui/expr/compound-assignment/eval-order.rs diff --git a/src/items/enumerations.md b/src/items/enumerations.md index 28d3ba873..0d42bfd05 100644 --- a/src/items/enumerations.md +++ b/src/items/enumerations.md @@ -13,8 +13,8 @@ > > _EnumItem_ :\ >    _OuterAttribute_\* [_Visibility_]?\ ->    [IDENTIFIER] ( _EnumItemTuple_ | _EnumItemStruct_ -> | _EnumItemDiscriminant_ )? +>    [IDENTIFIER] ( _EnumItemTuple_ | _EnumItemStruct_ )? +> _EnumItemDiscriminant_? > > _EnumItemTuple_ :\ >    `(` [_TupleFields_]? `)` @@ -56,22 +56,71 @@ a = Animal::Cat { name: "Spotty".to_string(), weight: 2.7 }; ``` In this example, `Cat` is a _struct-like enum variant_, whereas `Dog` is simply -called an enum variant. Each enum instance has a _discriminant_ which is an -integer associated to it that is used to determine which variant it holds. An -opaque reference to this discriminant can be obtained with the -[`mem::discriminant`] function. +called an enum variant. -## Custom Discriminant Values for Fieldless Enumerations +An enum where no constructors contain fields are called a +*field-less enum*. For example, this is a fieldless enum: -If there is no data attached to *any* of the variants of an enumeration, -then the discriminant can be directly chosen and accessed. +```rust +enum Fieldless { + Tuple(), + Struct{}, + Unit, +} +``` + +If a field-less enum only contains unit variants, the enum is called an +*unit-only enum*. For example: + +```rust +enum Enum { + Foo = 3, + Bar = 2, + Baz = 1, +} +``` + + +## Discriminants + +Each enum instance has a _discriminant_: an integer logically associated to it +that is used to determine which variant it holds. + +Under the [default representation], the discriminant is interpreted as +an `isize` value. However, the compiler is allowed to use a smaller type (or +another means of distinguishing variants) in its actual memory layout. + +### Assigning discriminant values + +#### Explicit discriminants -These enumerations can be cast to integer types with the `as` operator by a -[numeric cast]. The enumeration can optionally specify which integer each -discriminant gets by following the variant name with `=` followed by a [constant -expression]. If the first variant in the declaration is unspecified, then it is -set to zero. For every other unspecified discriminant, it is set to one higher -than the previous variant in the declaration. +In two circumstances, the discriminant of a variant may be explicitly set by +following the variant name with `=` and a [constant expression]: + + +1. if the enumeration is "[unit-only]". + + +2. if a [primitive representation] is used. For example: + + ```rust + #[repr(u8)] + enum Enum { + Unit = 3, + Tuple(u16), + Struct { + a: u8, + b: u16, + } = 1, + } + ``` + +#### Implicit discriminants + +If a discriminant for a variant is not specified, then it is set to one higher +than the discriminant of the previous variant in the declaration. If the +discriminant of the first variant in the declaration is unspecified, then +it is set to zero. ```rust enum Foo { @@ -84,10 +133,7 @@ let baz_discriminant = Foo::Baz as u32; assert_eq!(baz_discriminant, 123); ``` -Under the [default representation], the specified discriminant is interpreted as -an `isize` value although the compiler is allowed to use a smaller type in the -actual memory layout. The size and thus acceptable values can be changed by -using a [primitive representation] or the [`C` representation]. +#### Restrictions It is an error when two variants share the same discriminant. @@ -122,7 +168,89 @@ enum OverflowingDiscriminantError2 { } ``` -## Zero-variant Enums +### Accessing discriminant + +#### Via `mem::discriminant` + +[`mem::discriminant`] returns an opaque reference to the discriminant of +an enum value which can be compared. This cannot be used to get the value +of the discriminant. + +#### Casting + +If an enumeration is [unit-only] (with no tuple and struct variants), then its +discriminant can be directly accessed with a [numeric cast]; e.g.: + +```rust +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); +``` + +[Field-less enums] can be casted if they do not have explicit discriminants, or where only unit variants are explicit. + +```rust +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 FieldlessWithDiscrimants { + First = 10, + Tuple(), + Second = 20, + Struct{}, + Unit, +} + +assert_eq!(10, FieldlessWithDiscrimants::First as u8); +assert_eq!(11, FieldlessWithDiscrimants::Tuple() as u8); +assert_eq!(20, FieldlessWithDiscrimants::Second as u8); +assert_eq!(21, FieldlessWithDiscrimants::Struct{} as u8); +assert_eq!(22, FieldlessWithDiscrimants::Unit as u8); +``` + +#### Pointer casting + +If the enumeration specifies a [primitive representation], then the +discriminant may be reliably accessed via unsafe pointer casting: + +```rust +#[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()); +``` + +## Zero-variant enums Enums with zero variants are known as *zero-variant enums*. As they have no valid values, they cannot be instantiated. @@ -181,8 +309,10 @@ enum E { [enumerated type]: ../types/enum.md [`mem::discriminant`]: ../../std/mem/fn.discriminant.html [never type]: ../types/never.md +[unit-only]: #unit-only-enum [numeric cast]: ../expressions/operator-expr.md#semantics [constant expression]: ../const_eval.md#constant-expressions [default representation]: ../type-layout.md#the-default-representation [primitive representation]: ../type-layout.md#primitive-representations [`C` representation]: ../type-layout.md#the-c-representation +[Field-less enums]: #field-less-enum diff --git a/src/type-layout.md b/src/type-layout.md index 80a36abb8..191567a42 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -583,7 +583,7 @@ used with any other representation. [`Sized`]: ../std/marker/trait.Sized.html [`Copy`]: ../std/marker/trait.Copy.html [dynamically sized types]: dynamically-sized-types.md -[field-less enums]: items/enumerations.md#custom-discriminant-values-for-fieldless-enumerations +[field-less enums]: items/enumerations.md#field-less-enum [enumerations]: items/enumerations.md [zero-variant enums]: items/enumerations.md#zero-variant-enums [undefined behavior]: behavior-considered-undefined.md