Crates.io | enum-table |
lib.rs | enum-table |
version | 2.1.0 |
created_at | 2025-02-27 12:36:10.221075+00 |
updated_at | 2025-08-24 15:21:26.527107+00 |
description | A library for creating tables with enums as key. |
homepage | |
repository | https://github.com/moriyoshi-kasuga/enum-table |
max_upload_size | |
id | 1571672 |
size | 101,093 |
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.
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.
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.
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.
Add this to your Cargo.toml
:
[dependencies]
enum-table = "2.1"
Requires Rust 1.85 or later.
Enumable
TraitThe 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.
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!
macroYou 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");
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);
# }
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.
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.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.iter()
, iter_mut()
: Iterate over key-value pairs.keys()
: Iterate over keys.values()
, values_mut()
: Iterate over values.The EnumTable
can be converted to and from other standard collections.
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")));
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.
The enum-table
library is designed for performance:
derive
feature by default.Enumable
derive macro for automatic trait implementation.Licensed under the MIT license
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