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 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 aconst
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. Amatch
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 to0..N
indices, which is error-prone and less flexible.
Installation
Add this to your Cargo.toml
:
[]
= "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
.
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 ;
// Automatically implements the Enumable trait
// Recommended: specifies the discriminant size
// Runtime table creation
let mut table = COUNT }> new_with_fn;
assert_eq!;
let old_b = table.set;
assert_eq!;
assert_eq!;
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 ;
#
// This table is built at compile time and baked into the binary.
static TABLE: COUNT }> =
et!;
// Accessing the value is highly efficient as the table is pre-built.
const A_VAL: &str = TABLE.get;
assert_eq!;
Serde Support
Enable serde support by adding the serde
feature:
[]
= { = "2.1", = ["serde"] }
= "1.0"
#
# > new_with_fn;
// Serialize to JSON
let json = to_string.unwrap;
assert_eq!;
// Deserialize from JSON
let deserialized: COUNT }> =
from_str.unwrap;
assert_eq!;
# }
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 ;
// Using try_new_with_fn for fallible initialization
let result = COUNT }> try_new_with_fn;
assert!;
let = result.unwrap_err;
assert_eq!;
assert_eq!;
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 aVec<(K, V)>
.into_hash_map()
: Converts the table into aHashMap<K, V>
. Requires the enum key to implementEq + Hash
.into_btree_map()
: Converts the table into aBTreeMap<K, V>
. Requires the enum key to implementOrd
.
# use ;
#
# let table = new_with_fn;
// Example: Convert to a Vec
let vec = table.into_vec;
assert_eq!;
assert!;
To EnumTable
try_from_vec()
: Creates a table from aVec<(K, V)>
. Returns an error if any variant is missing or duplicated.try_from_hash_map()
: Creates a table from aHashMap<K, V>
. ReturnsNone
if the map does not contain exactly one entry for each variant.try_from_btree_map()
: Creates a table from aBTreeMap<K, V>
. ReturnsNone
if the map does not contain exactly one entry for each variant.
# use ;
# use HashMap;
#
// Example: Create from a HashMap
let mut map = new;
map.insert;
map.insert;
map.insert;
let table = try_from_hash_map.unwrap;
assert_eq!;
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