I just thought about enums. I might want to make an RFC, if some of these ideas sound useful.
What if we interpreted structs as single field enums?
A simple struct in rust looks like this:
struct Struct [...]
[...]
can either be a tuple like (val1, val2, ...)
, a named parameter list like { name1: val1, name2: val2, ... }
, or nothing.
When interpreting it as an enum, it could look like this:
enum Struct {
<default-variant-name> [...]
}
A mechanism, which would qualify some variant as default variant could be the name. I would suggest the name being the same as the struct:
enum Struct {
Struct [...]
}
Everything, that works for a struct, will now have to work for enums of this kind.
They might also work for different enums, like single value enums with a different variant name name, or with enums, where only one of the variants is called <default-variant-name>
.
An even simpler rule could always qualify the first variant as default variant.
So the default enum variant can be constructed in these two ways:
Struct [...]
Struct::<default-variant-name> [...]
Derives
Custom derives, which work for structs, do now also have to work for enums if they work for structs.
For example deriving Default
does currently not work for enums. This way, it would just derive default for the default variant.
Also writing your own custom derives will get simpler (assuming you can easily convert struct definitions to enum definitions for parsing). You don't have to write special cases for enums and structs. The version for enums will also work for structs. And if you don't want them to work with real enums, you could just restrict them to enums with a single variant.
Joining enums
Sometimes you might want to have similar enum variants in multiple enums. These enums might be subsets of other enums.
enum RoundShape {
Circle(f32),
Ellipse {
w: f32,
h: f32,
}
RoundedRectangle {
w: f32,
h: f32,
rad: f32,
}
}
enum RectangularShape {
Square(f32),
Rectangle {
w: f32,
h: f32,
}
RoundedRectangle {
w: f32,
h: f32,
rad: f32,
}
}
enum Shape = RoundShape | RectangularShape;
The last line would become something like this:
enum Shape {
Circle(f32),
Ellipse {
w: f32,
h: f32,
}
Square(f32),
Rectangle {
w: f32,
h: f32,
}
RoundedRectangle {
w: f32,
h: f32,
rad: f32,
}
}
The variant, which is available in both categories, will just be merged once. If the fields of the shared variant differ, it's an error.
Besides it could define some converters like this:
impl From<RoundShape> for Shape {
...
}
impl TryInto<RoundShape> for Shape {
...
}
impl From<RectangularShape> for Shape {
...
}
impl TryInto<RectangularShape> for Shape {
...
}
This simplifies sharing enums.
Using both features together
Example
Often you would like to be able to use enum variants as structs. For example in an enum like this:
enum Shape {
Circle(f32),
Ellipse {
w: f32,
h: f32,
}
Square(f32),
Rectangle {
w: f32,
h: f32,
}
RoundedRectangle {
w: f32,
h: f32,
rad: f32,
}
}
Since this is not possible in current rust, you would end up using something like this:
struct Circle(f32);
struct Ellipse {
w: f32,
h: f32,
}
struct Square(f32);
struct Rectangle {
w: f32,
h: f32,
}
struct RoundedRectangle {
w: f32,
h: f32,
rad: f32,
}
enum Shape {
Circle(Circle),
Ellipse(Ellipse),
Square(Square),
Rectangle(Rectangle),
RoundedRectangle(RoundedRectangle),
}
That's probably an extreme example. In most cases you would just replace a few variants this way.
Most features of enums are now pretty unnecessary. You only have single value variants, where the type of that value has the same name as the variant.
Alternatively you could define your enum like before and maybe add some converters from and to every single type.
But when you want to add a field to one struct, you would have to add the same field to all of your enums (you might want to have more than one of these enums.)
Solution
So when structs are interpreted as single field enums, you can do something like this:
struct Circle(f32);
struct Ellipse {
w: f32,
h: f32,
}
struct Square(f32);
struct Rectangle {
w: f32,
h: f32,
}
struct RoundedRectangle {
w: f32,
h: f32,
rad: f32,
}
enum RoundShape = Circle | Ellipse | RoundedRectangle;
enum RectangularShape = Square | Rectangle | RoundedRectangle;
enum Shape = Circle | Ellipse | Square | Rectangle | RoundedRectangle;
This might be a good alternative to enum variant types.
More ideas
Restricted
A restricted version could also allow enums defined this way to support only real structs as arguments and would not require interpreting structs.
So the solution example would still be possible and useful.
When thinking about it, this might even be a better solution.
Tuple matching
Splitting struct name and fields to tuples or named tuples when matching could make this even more elegant. You just don't specify the argument:
enum Enum {
Unit,
Tuple(...),
Named {...},
}
use Enum::*;
let some_enum: Enum = ...;
match some_enum {
Unit unit => process_unit(unit),
Tuple tuple => process_tuple(tuple),
Named named => process_named(named),
}
Thes match would be the same as this:
match some_enum {
Unit => process_unit(()),
Tuple(a, b, ...) => process_tuple((a, b, ...)),
Named { a, b, ... } => process_named( { a: a, b: b, ... } ),
}
The benefit is, it might be possible to change, what kind of struct a struct is, without having to change something in the match.
This kind of matching would also apply to structs, not only enum variants.
The problem is, we don't support named tuples.
Struct matching
If a struct is defined, using the restricted method of defining new enums, a similar matching method would work:
struct Unit;
struct Tuple(...);
struct Named {...};
enum Enum = Unit | Tuple | Named;
let some_enum: Enum = ...;
match some_enum {
unit: Unit => unit.process(),
tuple: Tuple => tuple.process(),
named: Named => named.process(),
}
Specifying the struct type of the variant is only possible if the enum is defined as a variant of multiple structs and only necessary, if you want to get the whole referenced struct.
You could still use the old way of pattern matching.
When I see this, I wonder, why rust hasn't done it that way from the beginning. Probably because it would be too difficult to implement C-like enums that way.
Conclusion
I think, none of these ideas would be a breaking change to rust. But my favorite addition is rather a small change. Enums are still defined like they currently are, but you can use structs to import new enum variants named by the structs. Besides you can also match the struct types for the imported variants.
struct StructUnit;
struct StructTuple(...);
struct StructNamed {...};
enum Enum {
use StructUnit;
use StructTuple(...);
use StructNamed {...};
EnumUnit,
EnumTuple(...),
EnumNamed {...},
}
let some_enum: Enum = ...;
match some_enum {
unit: StructUnit => unit.process(),
tuple: StructTuple => tuple.process(),
named: StructNamed => named.process(),
Enum::EnumUnit => process_enum_unit(),
Enum::EnumTuple(a, b, ...) => process_enum_tuple(a, b, ...),
Enum::EnumNamed { a, b, ... } => process_enum_named(a, b, ...),
}
This might even be compatible with RFC #2593