enum-table 2.1.0

A library for creating tables with enums as key.
Documentation
# enum-table

[![enum-table on crates.io][cratesio-image]][cratesio]
[![enum-table on docs.rs][docsrs-image]][docsrs]

[cratesio-image]: https://img.shields.io/crates/v/enum-table.svg
[cratesio]: https://crates.io/crates/enum-table
[docsrs-image]: https://docs.rs/enum-table/badge.svg
[docsrs]: https://docs.rs/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](./CHANGELOG.md) 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`:

```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`.

```rust
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

```rust
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.

```rust
# 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:

```toml
[dependencies]
enum-table = { version = "2.1", features = ["serde"] }
serde_json = "1.0"
```

```rust
# #[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.

```rust
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](https://docs.rs/enum-table/latest/enum_table/struct.EnumTable.html).
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`.

```rust
# 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.

```rust
# 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](https://docs.rs/enum-table/latest/enum_table/struct.EnumTable.html).

## 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](https://github.com/moriyoshi-kasuga/enum-table/blob/main/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.

<details>
<summary>Benchmark results</summary>

```text
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
```

</details>