#enums #tags #tagged #discriminant #c-like #proc-macro

no-std enum-tag

Proc. macro for generating enum discriminant types.

3 releases (breaking)

0.3.0 Mar 28, 2023
0.2.0 Mar 24, 2023
0.1.0 Mar 23, 2023

#1172 in Data structures

Download history 14/week @ 2024-03-14 20/week @ 2024-03-21 36/week @ 2024-03-28 39/week @ 2024-04-04 28/week @ 2024-04-25 1118/week @ 2024-05-02 5889/week @ 2024-05-09 4300/week @ 2024-05-16 6719/week @ 2024-05-23 5041/week @ 2024-05-30 873/week @ 2024-06-06 516/week @ 2024-06-13 825/week @ 2024-06-20 357/week @ 2024-06-27

2,820 downloads per month

MIT license

7KB

[2]: https://github.com/Robbepop/enum-tag/actions/workflows/rust.yml [4]: https://docs.rs/enum-tag [6]: https://crates.io/crates/enum-tag

Continuous Integration Documentation Crates.io
[ci][2] [docs][4] [crates][6]

#[derive(EnumTag)]

This crate provides a proc. macro to derive the EnumTag trait for the given Rust enum. The #derive(EnumTag) proc. macro only works on Rust enum types and generates both

  • a C-like enum type with the same variants as the input Rust enum but without all the associated data.
  • a derived implementation of the EnumTag trait for the Rust enum

The derived EnumTag trait makes it possible to create instances of the generated C-like enum type as well as link to its definition via <RustEnum as EnumTag>::Tag.

When is this useful?

This is mostly useful for crates that profit from having a distinct enum tag type while at the same time hosting Rust enum types with lots of variants which would make it burdensome to maintain the mirroring between both enum type and enum tag type.

The motivation for this crate was a Wasm interpreter that represents its instructions as an enum but also wants to access the opcodes of the instructions without their data. In this example the opcodes are the instruction enum tag.

Example

use ::enum_tag::EnumTag;

#[derive(EnumTag)]
#[repr(u8)] // Rust needs this for `B = 42`
enum Foo {
    A,
    B = 42,
    C(i32),
    D(i32, i64),
    E { a: i32 },
    F { a: i32, b: i64 },
}

/// This is how we can access the generated C-like enum type and name it.
type FooTag = <Foo as EnumTag>::Tag;

assert_eq!(FooTag::A, Foo::A.tag());
assert_eq!(FooTag::B, Foo::B.tag());
assert_eq!(FooTag::C, Foo::C(1).tag());
assert_eq!(FooTag::D, Foo::D(2, 3).tag());
assert_eq!(FooTag::E, Foo::E { a: 4 }.tag());
assert_eq!(FooTag::F, Foo::F { a: 5, b: 6 }.tag());

assert_eq!(FooTag::B as u8, 42);

The above #[derive(EnumTag)] generates the following Rust code:

const _: () = {
    #[derive(
        ::core::fmt::Debug,
        ::core::clone::Clone,
        ::core::marker::Copy,
        ::core::cmp::PartialEq,
        ::core::cmp::Eq,
        ::core::cmp::PartialOrd,
        ::core::cmp::Ord,
        ::core::hash::Hash,
    )]
    pub enum FooTag {
        A,
        B = 42,
        C,
        D,
        E,
        F,
    }

    impl ::enum_tag::EnumTag for Foo {
        type Tag = FooTag;

        fn tag(&self) -> <Self as ::enum_tag::EnumTag>::Tag {
            match self {
                Self::A { .. } => <Self as ::enum_tag::EnumTag>::Tag::A,
                Self::B { .. } => <Self as ::enum_tag::EnumTag>::Tag::B,
                Self::C { .. } => <Self as ::enum_tag::EnumTag>::Tag::C,
                Self::D { .. } => <Self as ::enum_tag::EnumTag>::Tag::D,
                Self::E { .. } => <Self as ::enum_tag::EnumTag>::Tag::E,
                Self::F { .. } => <Self as ::enum_tag::EnumTag>::Tag::F,
            }
        }
    }
};

Dependencies

~260–700KB
~17K SLoC