Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update documentation for arbitrary_enum_discriminant feature #1055

Merged
merged 9 commits into from
Jan 8, 2023
2 changes: 1 addition & 1 deletion src/const_eval.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/expressions/operator-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| [Unit-only enum] | 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 |
Expand Down Expand Up @@ -643,6 +643,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 enum]: ../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
Expand Down
137 changes: 118 additions & 19 deletions src/items/enumerations.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
>
> _EnumItem_ :\
> &nbsp;&nbsp; _OuterAttribute_<sup>\*</sup> [_Visibility_]<sup>?</sup>\
> &nbsp;&nbsp; [IDENTIFIER]&nbsp;( _EnumItemTuple_ | _EnumItemStruct_
> | _EnumItemDiscriminant_ )<sup>?</sup>
> &nbsp;&nbsp; [IDENTIFIER]&nbsp;( _EnumItemTuple_ | _EnumItemStruct_ )<sup>?</sup>
> _EnumItemDiscriminant_<sup>?</sup>
>
> _EnumItemTuple_ :\
> &nbsp;&nbsp; `(` [_TupleFields_]<sup>?</sup> `)`
Expand Down Expand Up @@ -56,22 +56,70 @@ 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
*<a id="field-less-enum">field-less enum</a>*. 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,
}
```

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.
If a field-less enum only contains unit variants, the enum is called an
*<a id="unit-only-enum">unit-only enum</a>*. 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

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 {
Expand All @@ -84,10 +132,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.

Expand Down Expand Up @@ -122,6 +167,59 @@ enum OverflowingDiscriminantError2 {
}
```

### 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);
```

#### 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) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right to me? Just because it's repr(u8) I don't think that means that the discriminant is necessarily first.

If the enum were repr(C) or repr(C, u8) then it'd be ok because the field order would be specified. But for repr(Rust) I'm pretty sure we're allowed to put the discriminant after the payload, if we wanted to.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, never mind, it looks like https://rust-lang.github.io/rfcs/2195-really-tagged-unions.html#guide-level-explanation means that repr(u8) disables all field reordering too, not just sets the discriminant size :(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that odd, too. The RFC justifies this by saying the discriminant position needs to be deterministic, which seems possible to do without disabling other field reordering?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(At this point it seems too late to change, but an edition change could require repr(C) for deterministic field ordering.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would need another flag to distinguish between the union-of-structs and struct-containing-union-of-structs layouts, though. Which might be nice, though!

I suppose the argument is that the size of the discriminant can't matter to safe code, so the only point of ever specifying its width is to get deterministic layout?

}
}

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
Expand Down Expand Up @@ -181,6 +279,7 @@ 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
Expand Down
2 changes: 1 addition & 1 deletion src/type-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down