enum-table 2.1.0

A library for creating tables with enums as key.
Documentation

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 logarithmic-time access.

See CHANGELOG for version history and recent updates.

Why use enum-table?

enum-table provides a specialized, efficient, and safe way to associate data with enum variants. Its design is centered around a key guarantee that differentiates it from other data structures.

Core Guarantee: Completeness

The core design principle of EnumTable is that an instance is guaranteed to hold a value for every variant of its enum key. This type-level invariant enables a cleaner and more efficient API.

For example, the [get()] method returns &V directly. This is in contrast to HashMap::get, which must return an Option<&V> because a key may or may not be present. With EnumTable, the presence of all keys is guaranteed, eliminating the need for unwrap() or other Option handling in your code.

If you need to handle cases where a value might not be present, you can use Option<V> as the value type: EnumTable<K, Option<V>, N>. This pattern is fully supported and provides a clear, explicit way to manage optional values.

Comparison with Alternatives

  • vs. HashMap<MyEnum, V>: Beyond the completeness guarantee, EnumTable has no heap allocations for its structure, offers better cache locality, and can be created in a const context for zero-cost initialization. HashMap is more flexible for dynamic data but comes with runtime overhead.

  • vs. match statements: EnumTable decouples data from logic. You can pass tables around, modify them at runtime, or load them from configurations. A match statement hardcodes the mapping and requires re-compilation to change.

  • vs. arrays ([V; N]): EnumTable works seamlessly with enums that have non-continuous or specified discriminants (e.g., enum E { A = 1, B = 100 }). An array-based approach requires manually mapping variants to 0..N indices, which is error-prone and less flexible.

Installation

Add this to your Cargo.toml:

[dependencies]
enum-table = "2.1"

Requires Rust 1.85 or later.

The Enumable Trait

The core of the library is the Enumable trait. It provides the necessary information about an enum—its variants and their count—to the EnumTable.

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

A critical requirement for this trait is that the VARIANTS array must be sorted by the enum's discriminant values. This ordering is essential for the table's binary search logic to function correctly.

Because manually ensuring this order is tedious and error-prone, it is strongly recommended to use the derive macro #[derive(Enumable)]. The derive macro automatically generates a correct, sorted VARIANTS array, guaranteeing proper behavior.

It is also recommended (though optional) to use a #[repr(u*)] attribute on your enum. This ensures the size and alignment of the enum's discriminant are stable and well-defined.

Usage Examples

Basic Usage

use enum_table::{EnumTable, Enumable};

#[derive(Enumable, Copy, Clone)] // Automatically implements the Enumable trait
#[repr(u8)] // Recommended: specifies the discriminant size
enum Test {
    A = 100, // You can specify custom discriminants
    B = 1,
    C,       // Will be 2 (previous value + 1)
}

// Runtime table creation
let mut table = EnumTable::<Test, &'static str, { Test::COUNT }>::new_with_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, Enumable};
# #[derive(Enumable, Copy, Clone)] #[repr(u8)] enum Test { A = 100, B = 1, C }
// This table is built at compile time and baked into the binary.
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",
  });

// Accessing the value is highly efficient as the table is pre-built.
const A_VAL: &str = TABLE.get(&Test::A);
assert_eq!(A_VAL, "A");

Serde Support

Enable serde support by adding the serde feature:

[dependencies]
enum-table = { version = "2.1", features = ["serde"] }
serde_json = "1.0"
# #[cfg(feature = "serde")]
# {
use enum_table::{EnumTable, Enumable};
use serde::{Serialize, Deserialize};

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

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

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

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

assert_eq!(table, deserialized);
# }

Error Handling and Alternative Constructors

The library provides several ways to create an EnumTable, some of which include built-in error handling for fallible initialization logic.

The example below shows try_new_with_fn, which is useful when each value is generated individually and might fail.

use enum_table::{EnumTable, Enumable};

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

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

assert!(result.is_err());
let (variant, error) = result.unwrap_err();
assert_eq!(variant, Color::Green);
assert_eq!(error, "Failed to get value for Green");

For other construction methods, such as creating a table from existing data structures, please see the API Overview section below and the full API documentation. For instance, you can use try_from_vec() or try_from_hash_map() from the Conversions API, which also handle potential errors like missing variants.

API Overview

Key Methods

  • EnumTable::new_with_fn(): Create a table by mapping each enum variant to a value.
  • EnumTable::try_new_with_fn(): Create a table with error handling support.
  • EnumTable::checked_new_with_fn(): Create a table with optional values.
  • EnumTable::get(): Access the value for a specific enum variant.
  • EnumTable::get_mut(): Get mutable access to a value.
  • EnumTable::set(): Update a value and return the old one.

Transformation

  • map(): Transforms all values in the table.
  • map_mut(): Transforms all values in the table in-place.
  • map_with_key(): Transforms values using both the key and value.
  • map_mut_with_key(): Transforms values in-place using both the key and value.

Iterators

  • iter(), iter_mut(): Iterate over key-value pairs.
  • keys(): Iterate over keys.
  • values(), values_mut(): Iterate over values.

Conversions

The EnumTable can be converted to and from other standard collections.

From EnumTable

  • into_vec(): Converts the table into a Vec<(K, V)>.
  • into_hash_map(): Converts the table into a HashMap<K, V>. Requires the enum key to implement Eq + Hash.
  • into_btree_map(): Converts the table into a BTreeMap<K, V>. Requires the enum key to implement Ord.
# use enum_table::{EnumTable, Enumable};
# #[derive(Enumable, Debug, PartialEq, Eq, Hash, Copy, Clone)] enum Color { Red, Green, Blue }
# let table = EnumTable::<Color, &'static str, 3>::new_with_fn(|c| match c {
#     Color::Red => "red", Color::Green => "green", Color::Blue => "blue",
# });
// Example: Convert to a Vec
let vec = table.into_vec();
assert_eq!(vec.len(), 3);
assert!(vec.contains(&(Color::Red, "red")));

To EnumTable

  • try_from_vec(): Creates a table from a Vec<(K, V)>. Returns an error if any variant is missing or duplicated.
  • try_from_hash_map(): Creates a table from a HashMap<K, V>. Returns None if the map does not contain exactly one entry for each variant.
  • try_from_btree_map(): Creates a table from a BTreeMap<K, V>. Returns None if the map does not contain exactly one entry for each variant.
# use enum_table::{EnumTable, Enumable};
# use std::collections::HashMap;
# #[derive(Enumable, Debug, PartialEq, Eq, Hash, Copy, Clone)] enum Color { Red, Green, Blue }
// Example: Create from a HashMap
let mut map = HashMap::new();
map.insert(Color::Red, 1);
map.insert(Color::Green, 2);
map.insert(Color::Blue, 3);

let table = EnumTable::<Color, i32, 3>::try_from_hash_map(map).unwrap();
assert_eq!(table.get(&Color::Green), &2);

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

Performance

The enum-table library is designed for performance:

  • Access Time: O(log N) lookup time via binary search of enum discriminants.
  • Memory Efficiency: No heap allocations for the table structure, leading to better cache locality.
  • Compile-Time Optimization: Static tables can be fully constructed at compile time.

Feature Flags

  • default: Enables the derive feature by default.
  • derive: Enables the Enumable derive macro for automatic trait implementation.
  • serde: Enables serialization and deserialization support using Serde.

License

Licensed under the MIT license

Benchmarks

Invoke the benchmarks using cargo bench to compare the performance of EnumTable with a HashMap for enum keys. The benchmarks measure the time taken for creating a table, getting values, and setting values.

EnumTable::new_with_fn  time:   [295.20 ps 302.47 ps 313.13 ps]
Found 4 outliers among 100 measurements (4.00%)
  2 (2.00%) high mild
  2 (2.00%) high severe

EnumTable::get          time:   [286.89 ps 287.14 ps 287.50 ps]
Found 12 outliers among 100 measurements (12.00%)
  5 (5.00%) high mild
  7 (7.00%) high severe

HashMap::get            time:   [7.7062 ns 7.7122 ns 7.7188 ns]
Found 8 outliers among 100 measurements (8.00%)
  3 (3.00%) high mild
  5 (5.00%) high severe

EnumTable::set          time:   [287.01 ps 287.12 ps 287.25 ps]
Found 12 outliers among 100 measurements (12.00%)
  1 (1.00%) low mild
  3 (3.00%) high mild
  8 (8.00%) high severe

HashMap::insert         time:   [9.2064 ns 9.2242 ns 9.2541 ns]
Found 4 outliers among 100 measurements (4.00%)
  2 (2.00%) high mild
  2 (2.00%) high severe