In rust PR #31179, @jseyfried and @petrochenkov discuss the use of enum variants through type aliases, which currently doesn’t work. It was closed pending an RFC since there were a number of unresolved use cases. Here’s an attempt at creating such an RFC; I’m still fairly new at this, so any feedback is appreciated.
- Feature Name: type_alias_enum_variants
- Start Date: 2017-12-28
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
Summary
This RFC proposes to allow access to enum variants through type aliases. This enables better abstraction/information hiding by encapsulating enums in aliases without having to create another enum type and requiring the conversion from and into the “alias” enum.
Motivation
While type aliases provide a useful means of encapsulating a type definition in order to hide implementation details or provide a more ergonomic API, the substitution principle currently falls down in the face of enum variants. It’s reasonable to expect that a type alias can fully replace the original type specification, and so the lack of working support for aliased enum variants represents an ergonomic gap in the language/type system. This can be useful in exposing an interface from a dependency to library users while “hiding” the exact implementation details. There’s at least some evidence that people have asked about this capability before.
Since Self
also works as an alias, this should also enable the use of Self
in more places.
Guide-level explanation
In general, the simple explanation here is that type aliases can be used in more places where you currently have to go through the original type definition, as it relates to enum variants. As much as possible, enum variants should work as if the original type was specified rather than the alias. This should make type aliases easier to learn than before, because there are fewer exceptions to their applicability.
enum Foo {
Bar(i32),
Baz { i: i32 },
}
type Alias = Foo;
fn main() {
let t = Alias::Bar(0);
let t = Alias::Baz { i: 0 };
match t {
Alias::Bar(_i) => {}
Alias::Baz { i: _i } => {}
}
}
Reference-level explanation
If a path refers into an alias, the behavior for enum variants should be as if the alias was substituted with the original type. Here are some examples of the new behavior in edge cases:
type Alias<T> = Option<T>;
Option::<u8>::None // Currently prohibited
Option::None::<u8> // Ok
Alias::<u8>::None // Will also be prohibited
Alias::None::<u8> // Will be ok
Drawbacks
We should not do this if the edge cases make the implemented behavior too complex or surprising to reason about the alias substitution.
Rationale and alternatives
This design seems like straightforward extension of what type aliases are supposed to be for. In that sense, the main alternative seems to be to do nothing. Currently, there are two ways to work around this:
-
Require the user to implement wrapper
enum
s instead of using aliases. This hides more information, so it may provide more API stability. On the other hand, it also mandates boxing and unboxing which has a run-time performance cost; and API stability is already up to the user in most other cases. -
Renaming of types via
use
statements. This provides a good solution in the case where there are no type variables that you want to fill in as part of the alias, but filling in variables is part of the motivating use case for having aliases.
As such, not implementing aliased enum variants this makes it harder to encapsulate or hide parts of an API.
Unresolved questions
From @petrochenkov’s questions in the PR discussion:
Alias::<u8>::None::<u8> // Not sure where/how this would be used
Alias::<u8>::None::<u16> // Not sure where/how this would be used
Type::AssociatedType::Variant // How is this different from other cases?