table_enum

Crates.iotable_enum
lib.rstable_enum
version0.3.0
sourcesrc
created_at2023-07-28 19:26:56.426945
updated_at2024-07-04 05:26:53.295677
descriptionA convenient rust macro to create enums with associated constant data (note: this is different from normal rust enums which are really tagged unions)
homepagehttps://github.com/sirwhinesalot/table-enum
repositoryhttps://github.com/sirwhinesalot/table-enum
max_upload_size
id928792
size8,284
César Santos (sirwhinesalot)

documentation

README

table-enum

Icon

A convenient rust macro to create enums with associated constant data. It lets you associate constant values to enum variants, similar to how enums work in Java, or how X macros are often used in C.

Only the enum tag is ever passed around, the data is accessed through generated const fn functions that match the enum tag to the relevant data.

This is different from how enums are typically used in Rust, which are actually tagged unions. (also known as variant types or sum types in computer science theory).

When would you use this?

An example where non-tagged-union1 enums are very useful is compiler or interpreter development. For example:

use table_enum::table_enum;

table_enum! {
    enum BinaryOp(#[constructor] text: &'static str, precedence: i32, #[default] right_assoc: bool) {
        Add("+", 10, _),
        Sub("-", 10, _),
        Mul("*", 20, _),
        Div("/", 20, _),
        Pow("**", 30, true),
        ...
    }
}

How does it work?

The example above expands into the following code:

enum BinaryOp {
    Add,
    Sub,
    Mul,
    Div,
    Pow,
    ...
}
impl BinaryOp {
    const fn text(&self) -> &'static str {
        match self {
            BinaryOp::Add => "+",
            BinaryOp::Sub => "-",
            BinaryOp::Mul => "*",
            BinaryOp::Div => "/",
            BinaryOp::Pow => "**",
            ...
        }
    }
    pub fn new(text: &'static str) -> Option<Self> {
        match text {
            "+" => Some(BinaryOp::Add),
            "-" => Some(BinaryOp::Sub),
            "*" => Some(BinaryOp::Mul),
            "/" => Some(BinaryOp::Div),
            "**" => Some(BinaryOp::Pow),
            ...
            _ => None
        }
    }
    const fn precedence(&self) -> i32 {
        match self {
            BinaryOp::Add => 10,
            BinaryOp::Sub => 10,
            BinaryOp::Mul => 20,
            BinaryOp::Div => 20,
            BinaryOp::Pow => 30,
            ...
        }
    }
    // cannot be const because Default::default() is not const
    fn right_assoc(&self) -> bool {
        match self {
            BinaryOp::Add => bool::default(),
            BinaryOp::Sub => bool::default(),
            BinaryOp::Mul => bool::default(),
            BinaryOp::Div => bool::default(),
            BinaryOp::Pow => true,
            ...
        }
    }
}

Note that no more than one field can have the #[constructor] attribute. If a field has the #[constructor] attribute, that field must have different values for every enum tag.

Changelog

  • 0.3.0: Added #[constructor] attribute.
  • 0.2.0: Added #[option] and#[default] attributes to the enum field declarations, see the documentation for more.
  • 0.1.0: First version

Alternative Crates

  • enum-assoc: more powerful but less convenient.
  • const-table: similar idea but as an attribute macro. Honestly a better approach to the same problem syntax wise. Uses an Array of Structs layout instead of Struct of Arrays unfortunately.

Footnotes

  1. I really wish Rust and Swift hadn't called their tagged unions "enums". To me enums are meant to be used as in this macro. A tagged union should be a kind of union.

Commit count: 20

cargo fmt