Expand description
§enum-table
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 aconstcontext. The core has no dependency onallocorstdat all, so it works in#![no_std]environments without a global allocator. - Compared to
matchstatements: 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 to0..Nindices.
§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 returnNone, stopping at the firstNone.EnumTable::checked_from_pairs(): Create a table from(K, V)pairs, orNoneif a variant is missing or duplicated.EnumTable::from_elem(): Create a table with the sameCopyvalue for every variant.EnumTable::default(): Create a table filled with each variant’sDefaultvalue (requiresV: Default).
§Access
get(),get_mut(),set(): O(1) access to the value for a variant.get_const(),get_mut_const(),set_const():const fnequivalents, using binary search instead of O(1) lookup.Index/IndexMut(table[key], acceptingKor&K): shorthand forget/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 itsDefault(requiresV: Default).take(): Replaces a value with itsDefaultand returns the old value (requiresV: 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)>andExtend<(&K, &V)>for updating values from an iterator.
§Performance
#[derive(Enumerable)]overridesvariant_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 defaultvariant_index()implementation.- The
const fnvariants (get_const, etc.) always binary search instead, sincevariant_index()cannot be called from aconst 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_fnvs.HashMap::new+ inserting every entry).single_get/single_set: a single lookup/update on one key, measured in isolation (also comparesgetagainst theconst fnbinary-searchget_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: Enablesstdandderive.derive: Enables the#[derive(Enumerable)]macro.serde: EnablesSerialize/DeserializeforEnumTable. Impliesalloc.std: Builds againststdinstead of#![no_std]. Impliesalloc.alloc: Linksalloc, required byserde.
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
EnumTablefor$variantand$valueinside aconstblock.
Structs§
- Enum
Table - A fixed-size table holding one
Vper variant ofK.
Traits§
- Enumerable
- A
Copyenum whose variantsEnumTablecan enumerate and index by position.