num_enum icon indicating copy to clipboard operation
num_enum copied to clipboard

feature: const variants of into, from, try_from, and default

Open cosmicexplorer opened this issue 10 months ago • 2 comments

Motivation

num_enum is very effective for manipulating data representations at runtime. However, there are some cases where we might want this functionality in const contexts, such as when generating static values or composing num_enum types into other Copy structs which we also want to manipulate in const contexts.

Alternatives

  • Note that while I have wanted this feature for both https://github.com/signalapp/libsignal and https://github.com/zip-rs/zip2, I cannot say that it is truly necessary, just convenient.
  • Certain nightly features such as const_trait_impl and effects are able to avoid the need for parts of this, by making From/Into/Default impls const-compatible, so for my other work that uses nightly, I actually don't require these at all!
    • However, I don't believe those features are expected to be stabilized soon, and I think it makes sense to have explicit/separate const and non-const methods for num_enum derives until Rust has developed a more formal mechanism for manipulating const effects.

Implementation

Four new derive macros were added, along with auxiliary traits/structs in lib.rs as needed. Since traits can't define const fns, all of these instead generate a method on the enum directly:

  • ConstIntoPrimitive: const_into(self) -> #repr
  • ConstFromPrimitive: const_from(#repr) -> Self
  • ConstTryFromPrimitive: const_try_from(#repr) -> Result<Self, ConstTryFromPrimitiveError<Self>>
  • ConstDefault: const_default() -> Self
#[derive(num_enum::ConstIntoPrimitive)]
#[repr(u8)]
pub enum E {
  Zero = 0,
  One = 1,
}

const e: u8 = E::Zero.const_into();
assert_eq!(e, 0);

Additionally, the #[num_enum(method_names(...)] attribute was added to the parser, to override the names of generated const methods. This is done because unlike the non-const derives, we do not have a canonical trait to implement, so we risk stomping on users' method names:

#[derive(num_enum::ConstIntoPrimitive)]
#[num_enum(method_names(const_into = f))]
#[repr(u8)]
pub enum E {
  Zero = 0,
  One = 1,
}

const e: u8 = E::Zero.f();
assert_eq!(e, 0);

Result

The boilerplate that num_enum generates no longer has be written by hand when using a num_enum type in const contexts!

cosmicexplorer avatar May 03 '24 12:05 cosmicexplorer