There's been a few attempts [1] [2] to allow using enum variants as types. These were rejected or postponed due to various issues and concerns of feature creep.
The main reason why this feature would be useful is to be able to pass specific enum variants as function arguments. Right now there's two ways:
- Use the enum in the function signature, but expect it to always be the right variant:
fn use_cat(cat: Animal) { match cat { Cat { meow } => { }, _ => unreachable!(), } }
- Emulate variants as types with structs:
struct Cat { meow: bool, } struct Dog { bark: bool, } enum Animal { Cat(Cat), Dog(Dog), }
What if the second approach was "blessed" with syntactic sugar to make it more convenient to use? Consider the following:
struct Foo {
foo: i8,
}
mod bar {
struct Bar {
bar: i32,
}
}
// Desugars into:
// enum FooBar {
// Foo(Foo),
// Bar(bar::Bar),
// Both(Foo, Bar),
// SpecialFoo(Foo),
// }
enum FooBar {
type Foo, // Use type as a variant
type bar::Bar, // Supports arbitrary paths
Both(Foo, Bar), // Can mix normal variants
SpecialFoo(Foo), // Can mix normal variants
}
fn assign() {
// Normal syntax works
let foo1 = FooBar::Foo(Foo { 1 });
// Desugars into:
// let foo2 = FooBar::Bar(bar::Bar { 2 });
let foo2 = FooBar::Bar { 2 };
// Is this too "clever"?
let foo3: FooBar = Foo { 3 };
// Or this?
let foo4: FooBar = bar::Bar { 4 };
}
fn use_foo(foo: Foo) {}
fn use_bar(bar: Bar) {}
fn match(fb: FooBar) {
match &fb {
// Desugars into:
// FooBar::Foo(f)
Foo f => { use_foo(f); },
// Desugars into:
// FooBar::Bar(b)
bar::Bar b => { use_bar(f); },
// Normal variants work as usual
FooBar::Both(f, b) => { },
SpecialFoo(f) => { },
}
match &fb {
// Allow destructuring as if it was a normal enum variant.
// Desugars into:
// FooBar::Foo(f) => { println!("{}", f.value); },
FooBar::Foo { value } => { println!("{value}"); },
_ => { },
}
}
fn if_let(fb: FooBar) {
// Desugars into:
// if let FooBar::Foo(f) = fb { }
if let Foo f = fb { }
// Desugars into:
// if let FooBar::Bar(b) = fb { }
if let bar::Bar b = fb { }
}
This is backwards compatible and transparent to downstream users. Enum variants with payload can be freely "promoted" into structs without breaking any existing code.
This approach adresses some of the concerns around feature creep in the previous attempts, such as what's described in this comment:
- Because these are regular structs, all normal functionality such as implementing traits on them is already supported.
- Arbitrary subsets of variants can be defined as distinct enums reusing the same structs as variants.
One drawback compared to the latest RFC attempt I can identify is there being a coercion cost, unlike if enum variants themselves were used as types with the discriminant "hardcoded" to them.
Types as enum variants on type system level allow some potentially interesting extensions. I don't know how useful this would be in practise though:
trait Sound {
fn sound();
}
struct Cat;
impl Sound for Cat {
fn sound() {
println!("meow");
}
}
struct Dog;
impl Sound for Dog {
fn sound() {
println!("bark");
}
}
enum Animal {
type Cat,
type Dog,
}
// Emits an error if:
// 1. Any variant isn't a type variant, or
// 2. Any type variant doesn't implement the trait
// Generates a trait implementation with every function consisting of a single exhaustive match block.
// "virtual" because it seemed the most fitting of all the keywords available (to me).
impl virtual Sound for Animal;
fn make_sound(animal: Animal) {
animal.sound();
}
fn test() {
// Prints "meow"
test(Cat);
}