Hi,
tl;dr: I'm trying to understand why rust has only partial support for C-like enums (= fieldless enums with a primitive representation), and whether there is any possibility for an RFC to close the gap at language level instead of forcing users to choose between myriad crates of varying degrees of complexity and quality.
=== Details below ===
The language officially supports primitive representation of fieldless enums (aka C-like enums). They can be defined (and the repr type is part of the public signature of the enum), and the compiler will honor the corresponding size and alignment requirements. Such enums can also be cast as
their repr type, which requires special treatment by the compiler because enums are neither primitive types nor trait objects (as the compiler helpfully points out when attempting to cast in the reverse direction):
error[E0605]: non-primitive cast:
u8
asMyEnum
an
as
expression can only be used to convert between primitive types or to coerce to a specific trait object
The one-way casting makes sense, because not every u8
value is a valid representation of any MyEnum
variant. But even the lossless direction that is supported lacks the corresponding From<u8> for MyEnum
.
This is in contrast with e.g. the primitive types that provide impl From
for all valid combinations of lossless/widening casts (which is much safer than relying on as
which is willing to perform lossy casts, such as truncating the fractional part of a f32
when casting as i32
).
The primitive types even provide myriad impl TryFrom
for narrowing casts, to cover the values that do allow for lossless conversions. But there is no corresponding impl TryFrom<MyEnum> for ReprType
when defining a C-like enum. Sure, those traits can be derived manually (or with a macro, perhaps provided by one of those many crates)... but why?
A major reason for defining a C-like enum at all, is because one needs to translate between the enum and its ordinal values (ie while implementing a binary specification of some kind). If rust had no concept of a C-like enum in the first place, that would be one thing (sad, but at least self-consistent). But the concept exists, and the language does the work to allow casting C-like enums to their repr type. It just seems to stop short of fully supporting the concept.
There have been lots of proposals and discussions over the years, and many (many) crates exist to emulate this rather basic behavior, but I got lost in the details and was unable to get a good sense of what the true blockers are and whether there is any possibility for a very simple RFC that would allow this:
#[repr(u8)]
enum MyEnum {
A = 0,
B = 1,
C = 3,
D = 4,
}
fn main() {
let o = MyEnum::A as u8; // 0u8 (already works today)
let o = u8::from(MyEnum::A); // 0u8
let e = MyEnum::try_from(0u8); // Ok(MyEnum::A)
let e = MyEnum::try_from(2u8); // Err, invalid repr value
let o = u16::from(MyEnum::A); // no such trait, convert to u8 first
let e = MyEnum::try_from(0u16); // no such trait, convert to u8 first
}
I guess the main technical questions would be:
- Should this work for any other enum types?
- A: NO. Only fieldless enums with a primitive representation have an unambiguous mapping to/from integers of the repr type.
- Should the conversion traits be derived unconditionally?
- A: Yes, this is a basic characteristic of C-like enums; also, complexity goes through the roof if we need to introduce additional annotations to control this behavior.
- Wouldn't this be a breaking change?
- A: Yes, because it would conflict with all existing implementations of those conversions; it would have to ship as part of a language edition bump
- What should the
ErrorType
of thatTryFrom
be?- A:
std::num::TryFromIntError
already exists for cases when "a checked integral type conversion fails" -- we could probably just use that. Callers can alwaysResult::map_err
it to a type they like better.
- A:
- If there's an
impl From<MyEnum> for u8
, should there not also be one foru16
,u32
, etc?- A: No, it adds complexity for little benefit; users who need them can easily -- and stably -- assemble them from existing conversions, once the basic conversions exist at all.