Skip to main content

Crate enum_table

Crate enum_table 

Source
Expand description

§enum-table

enum-table on crates.io enum-table on docs.rs

enum-table is a lightweight and efficient Rust library for mapping enums to values. It provides a fast, type-safe, and allocation-free alternative to using HashMap for enum keys, with compile-time safety and constant-time access (O(1)).

§Why use enum-table?

EnumTable<K, V, N> holds a value for every variant of K, so EnumTable::get returns &V directly instead of the Option<&V> that HashMap::get must return. If a value can legitimately be absent, use EnumTable<K, Option<V>, N> instead; EnumTable’s Default implementation then fills every slot with None.

  • Compared to HashMap<K, V>: no heap allocation for the table structure, better cache locality, and constructible in a const context. The core has no dependency on alloc or std at all, so it works in #![no_std] environments without a global allocator.
  • Compared to match statements: a table is data rather than code, so it can be passed around, mutated at runtime, or loaded from configuration without recompiling.
  • Compared to arrays ([V; N]): works with enums whose discriminants are non-contiguous or explicitly assigned (e.g. enum E { A = 1, B = 100 }), without manually mapping variants to 0..N indices.

§Installation

Add this to your Cargo.toml:

[dependencies]
enum-table = "4.0"

Requires Rust 1.85 or later.

§The Enumerable Trait

EnumTable’s key type must implement Enumerable, which lists every variant of the enum and gives each one an index:

pub unsafe trait Enumerable: Copy + 'static {
    const VARIANTS: &'static [Self];
    const COUNT: usize = Self::VARIANTS.len();

    fn variant_index(&self) -> usize { .. }
}

Enumerable is an unsafe trait: implementors must guarantee that Self has no padding bytes and that VARIANTS lists every variant of Self exactly once, sorted in ascending order by the unsigned bit-pattern of its in-memory representation. See the Enumerable trait documentation for the full safety contract, including how signed discriminants sort.

Use #[derive(Enumerable)] instead of implementing this trait by hand; it generates a correct unsafe impl with a sorted VARIANTS array and a compile-time-computed variant_index(), without requiring any unsafe code from you.

§Safety and Memory Layout

#[derive(Enumerable)] only supports field-less (C-like) enums, which never have padding bytes on their own. A primitive representation (e.g. #[repr(u8)]) is recommended for a stable, minimal-size layout, but not required for soundness.

#[repr(align(N))] is the exception: an alignment larger than the discriminant’s natural size can add trailing padding bytes that this crate’s byte-level comparisons would read as uninitialized memory, so the derive macro rejects it unconditionally at compile time.

use enum_table::Enumerable;

#[derive(Enumerable, Copy, Clone)]
#[repr(u8)] // <--- Recommended, but not required for soundness.
enum MyEnum {
    A,
    B,
}

§Usage Examples

§Basic Usage

use enum_table::{EnumTable, Enumerable};

#[derive(Enumerable, Copy, Clone)]
#[repr(u8)]
enum Test {
    A = 100, // You can specify custom discriminants
    B = 1,
    C,
}

let mut table = EnumTable::<Test, &'static str, { Test::COUNT }>::from_fn(
  |t| match t {
    Test::A => "A",
    Test::B => "B",
    Test::C => "C",
});

assert_eq!(table.get(Test::A), &"A");

let old_b = table.set(Test::B, "Changed B");
assert_eq!(old_b, "B");
assert_eq!(table.get(Test::B), &"Changed B");

§const Context and et! macro

You can create EnumTable instances at compile time with zero runtime overhead using the et! macro. This is ideal for static lookup tables.

use enum_table::{EnumTable, Enumerable};
#[derive(Enumerable, Copy, Clone)]
#[repr(u8)]
enum Test {
    A = 100,
    B = 1,
    C
}

static TABLE: EnumTable<Test, &'static str, { Test::COUNT }> =
  enum_table::et!(Test, &'static str, |t| match t {
      Test::A => "A",
      Test::B => "B",
      Test::C => "C",
  });

const A_VAL: &str = TABLE.get_const(Test::A);
assert_eq!(A_VAL, "A");

§Serde Support

Enable serde support by adding the serde feature:

[dependencies]
enum-table = { version = "4.0", features = ["serde"] }
serde_json = "1.0"
use enum_table::{EnumTable, Enumerable};
use serde::{Serialize, Deserialize};

#[derive(Debug, Enumerable, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
enum Status {
    Active,
    Inactive,
    Pending,
}

let table = EnumTable::<Status, &'static str, { Status::COUNT }>::from_fn(|status| match status {
    Status::Active => "running",
    Status::Inactive => "stopped",
    Status::Pending => "waiting",
});

let json = serde_json::to_string(&table).unwrap();
assert_eq!(json, r#"{"Active":"running","Inactive":"stopped","Pending":"waiting"}"#);

let deserialized: EnumTable<Status, &str, { Status::COUNT }> =
    serde_json::from_str(&json).unwrap();

assert_eq!(table, deserialized);

§Error Handling with try_from_fn

try_from_fn builds a table from a closure that may fail per variant, stopping at the first error:

use enum_table::{EnumTable, Enumerable};

#[derive(Enumerable, Copy, Clone, Debug, PartialEq)]
enum Color {
    Red,
    Green,
    Blue,
}

let result = EnumTable::<Color, &'static str, { Color::COUNT }>::try_from_fn(
    |color| match color {
        Color::Red => Ok("Red"),
        Color::Green => Err("Failed to get value for Green"),
        Color::Blue => Ok("Blue"),
    }
);

assert_eq!(result, Err("Failed to get value for Green"));

For other construction methods, such as creating a table from existing data structures, see the API Overview section below and the full API documentation.

§API Overview

For complete API documentation, visit EnumTable on doc.rs.

§Construction

  • EnumTable::from_fn(): Create a table by mapping each enum variant to a value.
  • EnumTable::try_from_fn(): Create a table from a closure that may fail, stopping at the first error.
  • EnumTable::checked_from_fn(): Create a table from a closure that may return None, stopping at the first None.
  • EnumTable::checked_from_pairs(): Create a table from (K, V) pairs, or None if a variant is missing or duplicated.
  • EnumTable::from_elem(): Create a table with the same Copy value for every variant.
  • EnumTable::default(): Create a table filled with each variant’s Default value (requires V: Default).

§Access

  • get(), get_mut(), set(): O(1) access to the value for a variant.
  • get_const(), get_mut_const(), set_const(): const fn equivalents, using binary search instead of O(1) lookup.
  • Index/IndexMut (table[key], accepting K or &K): shorthand for get/get_mut.

§Transformation

  • map(): Transforms all values in the table, given each key and value.
  • for_each(): Calls a function with each key and a reference to its value.
  • for_each_mut(): Mutates all values in the table in-place, given each key and value.
  • zip_with(): Combines two tables element-wise using a function, given each key and both values.
  • clear(): Resets every value to its Default (requires V: Default).
  • take(): Replaces a value with its Default and returns the old value (requires V: Default).

§Iterators

  • iter(), iter_mut(): Iterate over key-value pairs.
  • keys(): Iterate over keys.
  • values(), values_mut(): Iterate over values.
  • into_iter(): Consume the table and iterate over owned key-value pairs.
  • Implements Extend<(K, V)> and Extend<(&K, &V)> for updating values from an iterator.

§Performance

  • #[derive(Enumerable)] overrides variant_index() with a match whose arms resolve to their index at compile time, which tends to compile down to O(1) for enums with dense, sequential discriminants and to a comparison tree for sparse or custom ones — either way faster than the O(log N) binary search used by the default variant_index() implementation.
  • The const fn variants (get_const, etc.) always binary search instead, since variant_index() cannot be called from a const fn.
  • No heap allocation for the table structure, for better cache locality than HashMap.
  • Tables built with the et! macro are fully constructed at compile time.

§Benchmarks

  • construction: building a fully populated table/map from scratch (EnumTable::from_fn vs. HashMap::new + inserting every entry).
  • single_get / single_set: a single lookup/update on one key, measured in isolation (also compares get against the const fn binary-search get_const).
  • bulk_get_all_variants / bulk_set_all_variants: reading/writing every variant once per iteration, representing a whole-table workload rather than a single operation.
  • iteration: iterating over every key-value pair.
Benchmark results
construction/EnumTable::from_fn
                        time:   [3.7364 ns 3.7384 ns 3.7408 ns]
construction/HashMap (new + insert all)
                        time:   [75.696 ns 75.718 ns 75.744 ns]

single_get/EnumTable::get
                        time:   [477.00 ps 477.33 ps 477.66 ps]
single_get/EnumTable::get_const
                        time:   [2.2253 ns 2.2265 ns 2.2281 ns]
single_get/HashMap::get
                        time:   [6.7835 ns 6.7866 ns 6.7905 ns]

single_set/EnumTable::set
                        time:   [3.1015 ns 3.1114 ns 3.1222 ns]
single_set/HashMap::insert
                        time:   [9.7141 ns 9.7258 ns 9.7387 ns]

bulk_get_all_variants/EnumTable::get
                        time:   [2.3113 ns 2.3167 ns 2.3231 ns]
bulk_get_all_variants/HashMap::get
                        time:   [43.538 ns 43.555 ns 43.575 ns]

bulk_set_all_variants/EnumTable::set
                        time:   [21.568 ns 21.627 ns 21.688 ns]
bulk_set_all_variants/HashMap::insert
                        time:   [56.798 ns 56.849 ns 56.916 ns]

iteration/EnumTable::iter
                        time:   [594.43 ps 595.08 ps 595.83 ps]
iteration/HashMap::iter
                        time:   [3.8497 ns 3.8513 ns 3.8531 ns]

§Feature Flags

  • default: Enables std and derive.
  • derive: Enables the #[derive(Enumerable)] macro.
  • serde: Enables Serialize/Deserialize for EnumTable. Implies alloc.
  • std: Builds against std instead of #![no_std]. Implies alloc.
  • alloc: Links alloc, required by serde.

Disabling all of the above (default-features = false) builds enum-table as #![no_std] with no heap-allocation dependency, retaining the core EnumTable/Enumerable API.

§License

Licensed under the MIT license

Macros§

et
Builds an EnumTable for $variant and $value inside a const block.

Structs§

EnumTable
A fixed-size table holding one V per variant of K.

Traits§

Enumerable
A Copy enum whose variants EnumTable can enumerate and index by position.

Derive Macros§

Enumerable