- Start Date: 2015-01-21
- RFC PR: [rust-lang/rfcs#639](https://github.com/rust-lang/rfcs/pull/639)
- Rust Issue: [rust-lang/rust#24263](https://github.com/rust-lang/rust/issues/24263)

# Summary

Add a new intrinsic, `discriminant_value` that extracts the value of the discriminant for enum
types.

# Motivation

Many operations that work with discriminant values can be significantly improved with the ability to
extract the value of the discriminant that is used to distinguish between variants in an enum. While
trivial cases often optimise well, more complex ones would benefit from direct access to this value.

A good example is the `SqlState` enum from the `postgres` crate (Listed at the end of this RFC). It
contains 233 variants, of which all but one contain no fields. The most obvious implementation of
(for example) the `PartialEq` trait looks like this:

```rust
match (self, other) {
    (&Unknown(ref s1), &Unknown(ref s2)) => s1 == s2,
    (&SuccessfulCompletion, &SuccessfulCompletion) => true,
    (&Warning, &Warning) => true,
    (&DynamicResultSetsReturned, &DynamicResultSetsReturned) => true,
    (&ImplicitZeroBitPadding, &ImplicitZeroBitPadding) => true,
	      .
		  .
		  .
    (_, _) => false
}
```

Even with optimisations enabled, this code is very suboptimal, producing
[this code](https://gist.github.com/Aatch/c23a45634b10aaecad05). A way to extract the discriminant
would allow this code:

```rust
match (self, other) {
    (&Unknown(ref s1), &Unknown(ref s2)) => s1 == s2,
    (l, r) => unsafe {
	    discriminant_value(l) == discriminant_value(r)
	}
}
```

Which is compiled into [this IR](https://gist.github.com/Aatch/beb736b93a908aa67e84).

# Detailed design

## What is a discriminant?

A discriminant is a value stored in an enum type that indicates which variant the value is. The most
common case is that the discriminant is stored directly as an extra field in the variant. However,
the discriminant may be stored in any place, and in any format. However, we can always extract the
discriminant from the value somehow.

## Implementation

For any given type, `discriminant_value` will return a `u64` value. The values returned are as
specified:

* **Non-Enum Type**: Always 0
* **C-Like Enum Type**: If no variants have fields, then the enum is considered "C-Like". The user
  is able to specify discriminant values in this case, and the return value would be equivalent to
  the result of casting the variant to a `u64`.
* **ADT Enum Type**: If any variant has a field, then the enum is conidered to be an "ADT" enum. The
  user is not able to specify the discriminant value in this case. The precise values are
  unspecified, but have the following characteristics:

  * The value returned for the same variant of the same enum type will compare as
    equal. I.E. `discriminant_value(v) == discriminant_value(v)`.
  * Two values returned for different variants will compare as unequal relative to their respective
    listed positions. That means that if variant `A` is listed before variant `B`, then
    `discriminant_value(A) < discriminant_value(B)`.

Note the returned values for two differently-typed variants may compare in any way.

# Drawbacks

* Potentially exposes implementation details. However, relying the specific values returned from
`discriminant_value` should be considered bad practice, as the intrinsic provides no such guarantee.

* Allows non-enum types to be provided. This may be unexpected by some users.

# Alternatives

* More strongly specify the values returned. This would allow for a broader range of uses, but
  requires specifying behaviour that we may not want to.

* Disallow non-enum types. Non-enum types do not have a discriminant, so trying to extract might be
  considered an error. However, there is no compelling reason to disallow these types as we can
  simply treat them as single-variant enums and synthesise a zero constant. Note that this is what
  would be done for single-variant enums anyway.

* Do nothing. Improvements to codegen and/or optimisation could make this unnecessary. The
  "Sufficiently Smart Compiler" trap is a strong case against this reasoning though. There will
  likely always be cases where the user can write more efficient code than the compiler can produce.

# Unresolved questions

* Should `#[derive]` use this intrinsic to improve derived implementations of traits? While
  intrinsics are inherently unstable, `#[derive]`d code is compiler generated and therefore can be
  updated if the intrinsic is changed or removed.

# Appendix

```rust
pub enum SqlState {
    SuccessfulCompletion,
    Warning,
    DynamicResultSetsReturned,
    ImplicitZeroBitPadding,
    NullValueEliminatedInSetFunction,
    PrivilegeNotGranted,
    PrivilegeNotRevoked,
    StringDataRightTruncationWarning,
    DeprecatedFeature,
    NoData,
    NoAdditionalDynamicResultSetsReturned,
    SqlStatementNotYetComplete,
    ConnectionException,
    ConnectionDoesNotExist,
    ConnectionFailure,
    SqlclientUnableToEstablishSqlconnection,
    SqlserverRejectedEstablishmentOfSqlconnection,
    TransactionResolutionUnknown,
    ProtocolViolation,
    TriggeredActionException,
    FeatureNotSupported,
    InvalidTransactionInitiation,
    LocatorException,
    InvalidLocatorException,
    InvalidGrantor,
    InvalidGrantOperation,
    InvalidRoleSpecification,
    DiagnosticsException,
    StackedDiagnosticsAccessedWithoutActiveHandler,
    CaseNotFound,
    CardinalityViolation,
    DataException,
    ArraySubscriptError,
    CharacterNotInRepertoire,
    DatetimeFieldOverflow,
    DivisionByZero,
    ErrorInAssignment,
    EscapeCharacterConflict,
    IndicatorOverflow,
    IntervalFieldOverflow,
    InvalidArgumentForLogarithm,
    InvalidArgumentForNtileFunction,
    InvalidArgumentForNthValueFunction,
    InvalidArgumentForPowerFunction,
    InvalidArgumentForWidthBucketFunction,
    InvalidCharacterValueForCast,
    InvalidDatetimeFormat,
    InvalidEscapeCharacter,
    InvalidEscapeOctet,
    InvalidEscapeSequence,
    NonstandardUseOfEscapeCharacter,
    InvalidIndicatorParameterValue,
    InvalidParameterValue,
    InvalidRegularExpression,
    InvalidRowCountInLimitClause,
    InvalidRowCountInResultOffsetClause,
    InvalidTimeZoneDisplacementValue,
    InvalidUseOfEscapeCharacter,
    MostSpecificTypeMismatch,
    NullValueNotAllowedData,
    NullValueNoIndicatorParameter,
    NumericValueOutOfRange,
    StringDataLengthMismatch,
    StringDataRightTruncationException,
    SubstringError,
    TrimError,
    UnterminatedCString,
    ZeroLengthCharacterString,
    FloatingPointException,
    InvalidTextRepresentation,
    InvalidBinaryRepresentation,
    BadCopyFileFormat,
    UntranslatableCharacter,
    NotAnXmlDocument,
    InvalidXmlDocument,
    InvalidXmlContent,
    InvalidXmlComment,
    InvalidXmlProcessingInstruction,
    IntegrityConstraintViolation,
    RestrictViolation,
    NotNullViolation,
    ForeignKeyViolation,
    UniqueViolation,
    CheckViolation,
    ExclusionViolation,
    InvalidCursorState,
    InvalidTransactionState,
    ActiveSqlTransaction,
    BranchTransactionAlreadyActive,
    HeldCursorRequiresSameIsolationLevel,
    InappropriateAccessModeForBranchTransaction,
    InappropriateIsolationLevelForBranchTransaction,
    NoActiveSqlTransactionForBranchTransaction,
    ReadOnlySqlTransaction,
    SchemaAndDataStatementMixingNotSupported,
    NoActiveSqlTransaction,
    InFailedSqlTransaction,
    InvalidSqlStatementName,
    TriggeredDataChangeViolation,
    InvalidAuthorizationSpecification,
    InvalidPassword,
    DependentPrivilegeDescriptorsStillExist,
    DependentObjectsStillExist,
    InvalidTransactionTermination,
    SqlRoutineException,
    FunctionExecutedNoReturnStatement,
    ModifyingSqlDataNotPermittedSqlRoutine,
    ProhibitedSqlStatementAttemptedSqlRoutine,
    ReadingSqlDataNotPermittedSqlRoutine,
    InvalidCursorName,
    ExternalRoutineException,
    ContainingSqlNotPermitted,
    ModifyingSqlDataNotPermittedExternalRoutine,
    ProhibitedSqlStatementAttemptedExternalRoutine,
    ReadingSqlDataNotPermittedExternalRoutine,
    ExternalRoutineInvocationException,
    InvalidSqlstateReturned,
    NullValueNotAllowedExternalRoutine,
    TriggerProtocolViolated,
    SrfProtocolViolated,
    SavepointException,
    InvalidSavepointException,
    InvalidCatalogName,
    InvalidSchemaName,
    TransactionRollback,
    TransactionIntegrityConstraintViolation,
    SerializationFailure,
    StatementCompletionUnknown,
    DeadlockDetected,
    SyntaxErrorOrAccessRuleViolation,
    SyntaxError,
    InsufficientPrivilege,
    CannotCoerce,
    GroupingError,
    WindowingError,
    InvalidRecursion,
    InvalidForeignKey,
    InvalidName,
    NameTooLong,
    ReservedName,
    DatatypeMismatch,
    IndeterminateDatatype,
    CollationMismatch,
    IndeterminateCollation,
    WrongObjectType,
    UndefinedColumn,
    UndefinedFunction,
    UndefinedTable,
    UndefinedParameter,
    UndefinedObject,
    DuplicateColumn,
    DuplicateCursor,
    DuplicateDatabase,
    DuplicateFunction,
    DuplicatePreparedStatement,
    DuplicateSchema,
    DuplicateTable,
    DuplicateAliaas,
    DuplicateObject,
    AmbiguousColumn,
    AmbiguousFunction,
    AmbiguousParameter,
    AmbiguousAlias,
    InvalidColumnReference,
    InvalidColumnDefinition,
    InvalidCursorDefinition,
    InvalidDatabaseDefinition,
    InvalidFunctionDefinition,
    InvalidPreparedStatementDefinition,
    InvalidSchemaDefinition,
    InvalidTableDefinition,
    InvalidObjectDefinition,
    WithCheckOptionViolation,
    InsufficientResources,
    DiskFull,
    OutOfMemory,
    TooManyConnections,
    ConfigurationLimitExceeded,
    ProgramLimitExceeded,
    StatementTooComplex,
    TooManyColumns,
    TooManyArguments,
    ObjectNotInPrerequisiteState,
    ObjectInUse,
    CantChangeRuntimeParam,
    LockNotAvailable,
    OperatorIntervention,
    QueryCanceled,
    AdminShutdown,
    CrashShutdown,
    CannotConnectNow,
    DatabaseDropped,
    SystemError,
    IoError,
    UndefinedFile,
    DuplicateFile,
    ConfigFileError,
    LockFileExists,
    FdwError,
    FdwColumnNameNotFound,
    FdwDynamicParameterValueNeeded,
    FdwFunctionSequenceError,
    FdwInconsistentDescriptorInformation,
    FdwInvalidAttributeValue,
    FdwInvalidColumnName,
    FdwInvalidColumnNumber,
    FdwInvalidDataType,
    FdwInvalidDataTypeDescriptors,
    FdwInvalidDescriptorFieldIdentifier,
    FdwInvalidHandle,
    FdwInvalidOptionIndex,
    FdwInvalidOptionName,
    FdwInvalidStringLengthOrBufferLength,
    FdwInvalidStringFormat,
    FdwInvalidUseOfNullPointer,
    FdwTooManyHandles,
    FdwOutOfMemory,
    FdwNoSchemas,
    FdwOptionNameNotFound,
    FdwReplyHandle,
    FdwSchemaNotFound,
    FdwTableNotFound,
    FdwUnableToCreateExecution,
    FdwUnableToCreateReply,
    FdwUnableToEstablishConnection,
    PlpgsqlError,
    RaiseException,
    NoDataFound,
    TooManyRows,
    InternalError,
    DataCorrupted,
    IndexCorrupted,
    Unknown(String),
}
```

# History

This RFC was accepted on a provisional basis on 2015-10-04.  The
intention is to implement and experiment with the proposed
intrinsic. Some concerns expressed in the RFC discussion that will
require resolution before the RFC can be fully accepted:

- Using bounds such as `T:Reflect` to help ensure parametricity.
- Do we want to change the return type in some way?
  - It may not be helpful if we expose discriminant directly in the
    case of (potentially) negative discriminants.
  - We might want to return something more opaque to guard against
    unintended representation exposure.
- Does this intrinsic need to be unsafe?    
