Summary
Add conversion to and from underlying integer type on enums with the #[repr(u*)]
or #[repr(i*)]
attribute.
Motivation
Enums with the #[repr(u*)]
or #[repr(i*)]
attributes are commonly used for wrapping primitive integer values accepted by, or returned from, an underlying library when doing FFI. As such, it becomes a very common task to convert these Rust enum variants to their underlying integer representation, and to convert the integer representation into an instance of the enum. There is currently no really good way of doing either, in my opinion.
Since the language already guarantees that an enum with a repr
attribute will be represented in memory as the given integer type. I feel it’s quite natural that it would also provide ergonomic and safe access to this integer along with conversion from it. This would improve the FFI interoperability of Rust for at least my use cases.
EDIT: Below here I propose implementing From
/TryFrom
. But further down in drawbacks I realize this has some problems. So see proposed trait in drawbacks for what might technically work better.
Converting to integer
An enum with #[repr(u8)]
can already be easily converted into its integer value with my_enum as u8
. But it has at least three drawbacks:
- The
repr
type is not shown in the documentation for the type or anywhere else, so a user of this type must look at the code to know what to cast to. - If the type is ever changed to
#[repr(u16)]
or other larger types, themy_enum as u8
will continue to compile without a single warning, but it might now be silently truncating values. - The user doing the conversion must explicitly state the target type.
Automatically adding an impl From<MyEnum> for u8
to this enum would solve all three problems mentioned above. Now it can be used as unsafe { ffi_fn(my_enum.into()) }
. And to change the type one would only need to update the C library and the enum type declaration, none of the call sites.
Converting from integer
This operation is fallible (unless the enum happens to cover all possible values) and the only way I know to correctly implement it is to enumerate all possible values / variants in a match statement or if-else chain. This is tedious and prone to copy paste errors. It’s very much the type of code you want generated for you.
All macro based libraries I have tried for doing this for me have some limitations, or even bugs.
Adding an impl TryFrom<X> for Y
for all enums, Y
with #[repr(X)]
would eliminate the need to bring in third party libraries with their own set of problems and possibly dependencies, for doing this (in the type of code I write) very common task.
Drawbacks
-
These automatically derived trait implementations would become conflicting implementations for anyone who already manually implemented them. This could be fixed by requiring manually specifying
#[derive(From, TryFrom)]
or similar on the enums in question. Or by adding a new special trait to libstd for this very use case, so that no existing code already implements it:trait Primitive { type T; fn to_primitive(&self) -> Self::T; fn try_from_primitive(x: Self::T) -> Option<Self>; }
-
TryFrom
is not yet stabilized. Severely limiting the usefulness of at least half this proposal for now. But it looks likeTryFrom
very well could be stabilized before this even becomes a thing. This is also a non-issue with the custom trait proposed just above. -
TryFrom
will return aResult<MyEnum, E>
. So we would either need to setE
to()
, add a special error type for this to libstd, or emit a totally custom error type for each enum this is done for. It would likely be better to have the conversion from integer to enum returnOption<MyEnum>
, like most of the macro based crates for this does. This is also a non-issue with the custom trait proposed.
Prior art
There are a few macro-based crates that already do something similar, to what this proposal includes:
- https://crates.io/crates/derive-try-from-primitive
- https://crates.io/crates/enum-repr
- https://crates.io/crates/from-repr-enum-derive
- https://crates.io/crates/enum-primitive-derive
- https://crates.io/crates/enum_primitive
Something very similar to this has been discussed / merged a long long time ago. But that solution has been ripped out again: https://github.com/rust-lang/rust/pull/9250