enum_table/
lib.rs

1#![doc = include_str!(concat!("../", core::env!("CARGO_PKG_README")))]
2
3#[cfg(test)]
4pub extern crate self as enum_table;
5
6#[cfg(feature = "derive")]
7pub use enum_table_derive::Enumable;
8
9pub mod builder;
10mod intrinsics;
11
12pub mod __private {
13    pub use crate::intrinsics::sort_variants;
14}
15
16mod impls;
17pub use impls::*;
18
19mod macros;
20
21/// A trait for enumerations that can be used with `EnumTable`.
22///
23/// This trait requires that the enumeration provides a static array of its variants
24/// and a constant representing the count of these variants.
25pub trait Enumable: Copy + 'static {
26    const VARIANTS: &'static [Self];
27    const COUNT: usize = Self::VARIANTS.len();
28}
29
30/// A table that associates each variant of an enumeration with a value.
31///
32/// `EnumTable` is a generic struct that uses an enumeration as keys and stores
33/// associated values. It provides efficient logarithmic-time access (O(log N))
34/// to the values based on the enumeration variant. This is particularly useful
35/// when you want to map enum variants to specific values without the overhead
36/// of a `HashMap`.
37///
38/// # Guarantees and Design
39///
40/// The core design principle of `EnumTable` is that an instance is guaranteed to hold a
41/// value for every variant of the enum `K`. This guarantee allows for a cleaner API
42/// than general-purpose map structures.
43///
44/// For example, the [`Self::get`] method returns `&V` directly. This is in contrast to
45/// [`std::collections::HashMap::get`], which returns an `Option<&V>` because a key may or may not be
46/// present. With `EnumTable`, the presence of all keys (variants) is a type-level
47/// invariant, eliminating the need for `unwrap()` or other `Option` handling.
48///
49/// If you need to handle cases where a value might not be present or will be set
50/// later, you can use `Option<V>` as the value type: `EnumTable<K, Option<V>, N>`.
51/// The struct provides convenient methods like [`Self::new_fill_with_none`] for this pattern.
52///
53/// # Type Parameters
54///
55/// * `K`: The enumeration type that implements the `Enumable` trait. This trait
56///   ensures that the enum provides a static array of its variants and a count
57///   of these variants.
58/// * `V`: The type of values to be associated with each enum variant.
59/// * `N`: The number of variants in the enum, which should match the length of
60///   the static array of variants provided by the `Enumable` trait.
61///
62/// # Examples
63///
64/// ```rust
65/// use enum_table::{EnumTable, Enumable};
66///
67/// #[derive(Enumable, Copy, Clone)]
68/// enum Color {
69///     Red,
70///     Green,
71///     Blue,
72/// }
73///
74/// // Create an EnumTable using the new_with_fn method
75/// let table = EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
76///     Color::Red => "Red",
77///     Color::Green => "Green",
78///     Color::Blue => "Blue",
79/// });
80///
81/// // Access values associated with enum variants
82/// assert_eq!(table.get(&Color::Red), &"Red");
83/// assert_eq!(table.get(&Color::Green), &"Green");
84/// assert_eq!(table.get(&Color::Blue), &"Blue");
85/// ```
86pub struct EnumTable<K: Enumable, V, const N: usize> {
87    table: [(K, V); N],
88}
89
90impl<K: Enumable, V, const N: usize> EnumTable<K, V, N> {
91    /// Creates a new `EnumTable` with the given table of variants and values.
92    /// Typically, you would use the [`crate::et`] macro or [`crate::builder::EnumTableBuilder`] to create an `EnumTable`.
93    pub(crate) const fn new(table: [(K, V); N]) -> Self {
94        #[cfg(debug_assertions)]
95        const {
96            // Ensure that the variants are sorted by their discriminants.
97            // This is a compile-time check to ensure that the variants are in the correct order.
98            if !intrinsics::is_sorted(K::VARIANTS) {
99                panic!(
100                    "Enumable: variants are not sorted by discriminant. Use `enum_table::Enumable` derive macro to ensure correct ordering."
101                );
102            }
103        }
104
105        Self { table }
106    }
107
108    /// Create a new EnumTable with a function that takes a variant and returns a value.
109    /// If you want to define it in const, use [`crate::et`] macro
110    /// Creates a new `EnumTable` using a function to generate values for each variant.
111    ///
112    /// # Arguments
113    ///
114    /// * `f` - A function that takes a reference to an enumeration variant and returns
115    ///   a value to be associated with that variant.
116    pub fn new_with_fn(mut f: impl FnMut(&K) -> V) -> Self {
117        et!(K, V, { N }, |variant| f(variant))
118    }
119
120    /// Creates a new `EnumTable` using a function that returns a `Result` for each variant.
121    ///
122    /// This method applies the provided closure to each variant of the enum. If the closure
123    /// returns `Ok(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
124    /// If the closure returns `Err(e)` for any variant, the construction is aborted and
125    /// `Err((variant, e))` is returned, where `variant` is the enum variant that caused the error.
126    ///
127    /// # Arguments
128    ///
129    /// * `f` - A closure that takes a reference to an enum variant and returns a `Result<V, E>`.
130    ///
131    /// # Returns
132    ///
133    /// * `Ok(Self)` if all variants succeed.
134    /// * `Err((variant, e))` if any variant fails, containing the failing variant and the error.
135    pub fn try_new_with_fn<E>(mut f: impl FnMut(&K) -> Result<V, E>) -> Result<Self, (K, E)> {
136        Ok(et!(K, V, { N }, |variant| {
137            f(variant).map_err(|e| (*variant, e))?
138        }))
139    }
140
141    /// Creates a new `EnumTable` using a function that returns an `Option` for each variant.
142    ///
143    /// This method applies the provided closure to each variant of the enum. If the closure
144    /// returns `Some(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
145    /// If the closure returns `None` for any variant, the construction is aborted and
146    /// `Err(variant)` is returned, where `variant` is the enum variant that caused the failure.
147    ///
148    /// # Arguments
149    ///
150    /// * `f` - A closure that takes a reference to an enum variant and returns an `Option<V>`.
151    ///
152    /// # Returns
153    ///
154    /// * `Ok(Self)` if all variants succeed.
155    /// * `Err(variant)` if any variant fails, containing the failing variant.
156    pub fn checked_new_with_fn(mut f: impl FnMut(&K) -> Option<V>) -> Result<Self, K> {
157        Ok(et!(K, V, { N }, |variant| f(variant).ok_or(*variant)?))
158    }
159
160    pub(crate) const fn binary_search(&self, variant: &K) -> usize {
161        let mut low = 0;
162        let mut high = N;
163
164        while low < high {
165            let mid = low + (high - low) / 2;
166            if intrinsics::const_enum_lt(&self.table[mid].0, variant) {
167                low = mid + 1;
168            } else {
169                high = mid;
170            }
171        }
172
173        low
174    }
175
176    /// Returns a reference to the value associated with the given enumeration variant.
177    ///
178    /// # Arguments
179    ///
180    /// * `variant` - A reference to an enumeration variant.
181    pub const fn get(&self, variant: &K) -> &V {
182        let idx = self.binary_search(variant);
183        &self.table[idx].1
184    }
185
186    /// Returns a mutable reference to the value associated with the given enumeration variant.
187    ///
188    /// # Arguments
189    ///
190    /// * `variant` - A reference to an enumeration variant.
191    pub const fn get_mut(&mut self, variant: &K) -> &mut V {
192        let idx = self.binary_search(variant);
193        &mut self.table[idx].1
194    }
195
196    /// Sets the value associated with the given enumeration variant.
197    ///
198    /// # Arguments
199    ///
200    /// * `variant` - A reference to an enumeration variant.
201    /// * `value` - The new value to associate with the variant.
202    /// # Returns
203    /// The old value associated with the variant.
204    pub const fn set(&mut self, variant: &K, value: V) -> V {
205        let idx = self.binary_search(variant);
206        core::mem::replace(&mut self.table[idx].1, value)
207    }
208
209    /// Returns the number of generic N
210    pub const fn len(&self) -> usize {
211        N
212    }
213
214    /// Returns `false` since the table is never empty.
215    pub const fn is_empty(&self) -> bool {
216        false
217    }
218
219    /// Returns an iterator over references to the keys in the table.
220    pub fn keys(&self) -> impl Iterator<Item = &K> {
221        self.table.iter().map(|(discriminant, _)| discriminant)
222    }
223
224    /// Returns an iterator over references to the values in the table.
225    pub fn values(&self) -> impl Iterator<Item = &V> {
226        self.table.iter().map(|(_, value)| value)
227    }
228
229    /// Returns an iterator over mutable references to the values in the table.
230    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
231        self.table.iter_mut().map(|(_, value)| value)
232    }
233
234    /// Returns an iterator over mutable references to the values in the table.
235    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
236        self.table
237            .iter()
238            .map(|(discriminant, value)| (discriminant, value))
239    }
240
241    /// Returns an iterator over mutable references to the values in the table.
242    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
243        self.table
244            .iter_mut()
245            .map(|(discriminant, value)| (&*discriminant, value))
246    }
247
248    /// Transforms all values in the table using the provided function.
249    ///
250    /// This method consumes the table and creates a new one with values
251    /// transformed by the given closure.
252    ///
253    /// # Arguments
254    ///
255    /// * `f` - A closure that takes an owned value and returns a new value.
256    ///
257    /// # Examples
258    ///
259    /// ```rust
260    /// use enum_table::{EnumTable, Enumable};
261    ///
262    /// #[derive(Enumable, Copy, Clone)]
263    /// enum Size {
264    ///     Small,
265    ///     Medium,
266    ///     Large,
267    /// }
268    ///
269    /// let table = EnumTable::<Size, i32, { Size::COUNT }>::new_with_fn(|size| match size {
270    ///     Size::Small => 1,
271    ///     Size::Medium => 2,
272    ///     Size::Large => 3,
273    /// });
274    ///
275    /// let doubled = table.map(|value| value * 2);
276    ///
277    /// assert_eq!(doubled.get(&Size::Small), &2);
278    /// assert_eq!(doubled.get(&Size::Medium), &4);
279    /// assert_eq!(doubled.get(&Size::Large), &6);
280    /// ```
281    pub fn map<U>(self, mut f: impl FnMut(V) -> U) -> EnumTable<K, U, N> {
282        EnumTable::new(
283            self.table
284                .map(|(discriminant, value)| (discriminant, f(value))),
285        )
286    }
287
288    /// Transforms all values in the table using the provided function, with access to the key.
289    ///
290    /// This method consumes the table and creates a new one with values
291    /// transformed by the given closure, which receives both the key and the value.
292    ///
293    /// # Arguments
294    ///
295    /// * `f` - A closure that takes a key reference and an owned value, and returns a new value.
296    pub fn map_with_key<U>(self, mut f: impl FnMut(&K, V) -> U) -> EnumTable<K, U, N> {
297        EnumTable::new(
298            self.table
299                .map(|(discriminant, value)| (discriminant, f(&discriminant, value))),
300        )
301    }
302
303    /// Transforms all values in the table in-place using the provided function.
304    ///
305    /// # Arguments
306    ///
307    /// * `f` - A closure that takes a mutable reference to a value and modifies it.
308    ///
309    /// # Examples
310    ///
311    /// ```rust
312    /// use enum_table::{EnumTable, Enumable};
313    ///
314    /// #[derive(Enumable, Copy, Clone)]
315    /// enum Level {
316    ///     Low,
317    ///     Medium,
318    ///     High,
319    /// }
320    ///
321    /// let mut table = EnumTable::<Level, i32, { Level::COUNT }>::new_with_fn(|level| match level {
322    ///     Level::Low => 10,
323    ///     Level::Medium => 20,
324    ///     Level::High => 30,
325    /// });
326    ///
327    /// table.map_mut(|value| *value += 5);
328    ///
329    /// assert_eq!(table.get(&Level::Low), &15);
330    /// assert_eq!(table.get(&Level::Medium), &25);
331    /// assert_eq!(table.get(&Level::High), &35);
332    /// ```
333    pub fn map_mut(&mut self, mut f: impl FnMut(&mut V)) {
334        self.table.iter_mut().for_each(|(_, value)| {
335            f(value);
336        });
337    }
338
339    /// Transforms all values in the table in-place using the provided function, with access to the key.
340    ///
341    /// # Arguments
342    ///
343    /// * `f` - A closure that takes a key reference and a mutable reference to a value, and modifies it.
344    pub fn map_mut_with_key(&mut self, mut f: impl FnMut(&K, &mut V)) {
345        self.table.iter_mut().for_each(|(discriminant, value)| {
346            f(discriminant, value);
347        });
348    }
349}
350
351impl<K: Enumable, V, const N: usize> EnumTable<K, Option<V>, N> {
352    /// Creates a new `EnumTable` with `None` values for each variant.
353    pub const fn new_fill_with_none() -> Self {
354        et!(K, Option<V>, { N }, |variant| None)
355    }
356
357    /// Clears the table, setting each value to `None`.
358    pub fn clear_to_none(&mut self) {
359        for (_, value) in &mut self.table {
360            *value = None;
361        }
362    }
363}
364
365impl<K: Enumable, V: Copy, const N: usize> EnumTable<K, V, N> {
366    /// Creates a new `EnumTable` with the same copied value for each variant.
367    ///
368    /// This method initializes the table with the same value for each
369    /// variant of the enumeration. The value must implement `Copy`.
370    ///
371    /// # Arguments
372    ///
373    /// * `value` - The value to copy for each enum variant.
374    ///
375    /// # Examples
376    ///
377    /// ```rust
378    /// use enum_table::{EnumTable, Enumable};
379    ///
380    /// #[derive(Enumable, Copy, Clone)]
381    /// enum Status {
382    ///     Active,
383    ///     Inactive,
384    ///     Pending,
385    /// }
386    ///
387    /// let table = EnumTable::<Status, i32, { Status::COUNT }>::new_fill_with_copy(42);
388    ///
389    /// assert_eq!(table.get(&Status::Active), &42);
390    /// assert_eq!(table.get(&Status::Inactive), &42);
391    /// assert_eq!(table.get(&Status::Pending), &42);
392    /// ```
393    pub const fn new_fill_with_copy(value: V) -> Self {
394        et!(K, V, { N }, |variant| value)
395    }
396}
397
398impl<K: Enumable, V: Default, const N: usize> EnumTable<K, V, N> {
399    /// Creates a new `EnumTable` with default values for each variant.
400    ///
401    /// This method initializes the table with the default value of type `V` for each
402    /// variant of the enumeration.
403    pub fn new_fill_with_default() -> Self {
404        et!(K, V, { N }, |variant| V::default())
405    }
406
407    /// Clears the table, setting each value to its default.
408    pub fn clear_to_default(&mut self) {
409        for (_, value) in &mut self.table {
410            *value = V::default();
411        }
412    }
413}
414
415#[cfg(test)]
416mod tests {
417    use super::*;
418
419    #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
420    enum Color {
421        Red = 33,
422        Green = 11,
423        Blue = 222,
424    }
425
426    const TABLES: EnumTable<Color, &'static str, { Color::COUNT }> =
427        crate::et!(Color, &'static str, |color| match color {
428            Color::Red => "Red",
429            Color::Green => "Green",
430            Color::Blue => "Blue",
431        });
432
433    #[test]
434    fn new_with_fn() {
435        let table =
436            EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
437                Color::Red => "Red",
438                Color::Green => "Green",
439                Color::Blue => "Blue",
440            });
441
442        assert_eq!(table.get(&Color::Red), &"Red");
443        assert_eq!(table.get(&Color::Green), &"Green");
444        assert_eq!(table.get(&Color::Blue), &"Blue");
445    }
446
447    #[test]
448    fn try_new_with_fn() {
449        let table =
450            EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
451                |color| match color {
452                    Color::Red => Ok::<&'static str, core::convert::Infallible>("Red"),
453                    Color::Green => Ok("Green"),
454                    Color::Blue => Ok("Blue"),
455                },
456            );
457
458        assert!(table.is_ok());
459        let table = table.unwrap();
460
461        assert_eq!(table.get(&Color::Red), &"Red");
462        assert_eq!(table.get(&Color::Green), &"Green");
463        assert_eq!(table.get(&Color::Blue), &"Blue");
464
465        let error_table = EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
466            |color| match color {
467                Color::Red => Ok("Red"),
468                Color::Green => Err("Error on Green"),
469                Color::Blue => Ok("Blue"),
470            },
471        );
472
473        assert!(error_table.is_err());
474        let (variant, error) = error_table.unwrap_err();
475
476        assert_eq!(variant, Color::Green);
477        assert_eq!(error, "Error on Green");
478    }
479
480    #[test]
481    fn checked_new_with_fn() {
482        let table =
483            EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
484                match color {
485                    Color::Red => Some("Red"),
486                    Color::Green => Some("Green"),
487                    Color::Blue => Some("Blue"),
488                }
489            });
490
491        assert!(table.is_ok());
492        let table = table.unwrap();
493
494        assert_eq!(table.get(&Color::Red), &"Red");
495        assert_eq!(table.get(&Color::Green), &"Green");
496        assert_eq!(table.get(&Color::Blue), &"Blue");
497
498        let error_table =
499            EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
500                match color {
501                    Color::Red => Some("Red"),
502                    Color::Green => None,
503                    Color::Blue => Some("Blue"),
504                }
505            });
506
507        assert!(error_table.is_err());
508        let variant = error_table.unwrap_err();
509
510        assert_eq!(variant, Color::Green);
511    }
512
513    #[test]
514    fn get() {
515        assert_eq!(TABLES.get(&Color::Red), &"Red");
516        assert_eq!(TABLES.get(&Color::Green), &"Green");
517        assert_eq!(TABLES.get(&Color::Blue), &"Blue");
518    }
519
520    #[test]
521    fn get_mut() {
522        let mut table = TABLES;
523        assert_eq!(table.get_mut(&Color::Red), &mut "Red");
524        assert_eq!(table.get_mut(&Color::Green), &mut "Green");
525        assert_eq!(table.get_mut(&Color::Blue), &mut "Blue");
526
527        *table.get_mut(&Color::Red) = "Changed Red";
528        *table.get_mut(&Color::Green) = "Changed Green";
529        *table.get_mut(&Color::Blue) = "Changed Blue";
530
531        assert_eq!(table.get(&Color::Red), &"Changed Red");
532        assert_eq!(table.get(&Color::Green), &"Changed Green");
533        assert_eq!(table.get(&Color::Blue), &"Changed Blue");
534    }
535
536    #[test]
537    fn set() {
538        let mut table = TABLES;
539        assert_eq!(table.set(&Color::Red, "New Red"), "Red");
540        assert_eq!(table.set(&Color::Green, "New Green"), "Green");
541        assert_eq!(table.set(&Color::Blue, "New Blue"), "Blue");
542
543        assert_eq!(table.get(&Color::Red), &"New Red");
544        assert_eq!(table.get(&Color::Green), &"New Green");
545        assert_eq!(table.get(&Color::Blue), &"New Blue");
546    }
547
548    #[test]
549    fn keys() {
550        let keys: Vec<_> = TABLES.keys().collect();
551        assert_eq!(keys, vec![&Color::Green, &Color::Red, &Color::Blue]);
552    }
553
554    #[test]
555    fn values() {
556        let values: Vec<_> = TABLES.values().collect();
557        assert_eq!(values, vec![&"Green", &"Red", &"Blue"]);
558    }
559
560    #[test]
561    fn iter() {
562        let iter: Vec<_> = TABLES.iter().collect();
563        assert_eq!(
564            iter,
565            vec![
566                (&Color::Green, &"Green"),
567                (&Color::Red, &"Red"),
568                (&Color::Blue, &"Blue")
569            ]
570        );
571    }
572
573    #[test]
574    fn iter_mut() {
575        let mut table = TABLES;
576        for (key, value) in table.iter_mut() {
577            *value = match key {
578                Color::Red => "Changed Red",
579                Color::Green => "Changed Green",
580                Color::Blue => "Changed Blue",
581            };
582        }
583        let iter: Vec<_> = table.iter().collect();
584        assert_eq!(
585            iter,
586            vec![
587                (&Color::Green, &"Changed Green"),
588                (&Color::Red, &"Changed Red"),
589                (&Color::Blue, &"Changed Blue")
590            ]
591        );
592    }
593
594    #[test]
595    fn map() {
596        let table = EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
597            Color::Red => 1,
598            Color::Green => 2,
599            Color::Blue => 3,
600        });
601
602        let doubled = table.map(|value| value * 2);
603
604        assert_eq!(doubled.get(&Color::Red), &2);
605        assert_eq!(doubled.get(&Color::Green), &4);
606        assert_eq!(doubled.get(&Color::Blue), &6);
607    }
608
609    #[test]
610    fn map_with_key() {
611        let table = EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
612            Color::Red => 1,
613            Color::Green => 2,
614            Color::Blue => 3,
615        });
616
617        let mapped = table.map_with_key(|key, value| match key {
618            Color::Red => value + 10,   // 1 + 10 = 11
619            Color::Green => value + 20, // 2 + 20 = 22
620            Color::Blue => value + 30,  // 3 + 30 = 33
621        });
622
623        // Note: The order in the underlying table is based on discriminant value (Green, Red, Blue)
624        assert_eq!(mapped.get(&Color::Red), &11);
625        assert_eq!(mapped.get(&Color::Green), &22);
626        assert_eq!(mapped.get(&Color::Blue), &33);
627    }
628
629    #[test]
630    fn map_mut() {
631        let mut table =
632            EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
633                Color::Red => 10,
634                Color::Green => 20,
635                Color::Blue => 30,
636            });
637
638        table.map_mut(|value| *value += 5);
639
640        assert_eq!(table.get(&Color::Red), &15);
641        assert_eq!(table.get(&Color::Green), &25);
642        assert_eq!(table.get(&Color::Blue), &35);
643    }
644
645    #[test]
646    fn map_mut_with_key() {
647        let mut table =
648            EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
649                Color::Red => 10,
650                Color::Green => 20,
651                Color::Blue => 30,
652            });
653
654        table.map_mut_with_key(|key, value| {
655            *value += match key {
656                Color::Red => 1,   // 10 + 1 = 11
657                Color::Green => 2, // 20 + 2 = 22
658                Color::Blue => 3,  // 30 + 3 = 33
659            }
660        });
661
662        assert_eq!(table.get(&Color::Red), &11);
663        assert_eq!(table.get(&Color::Green), &22);
664        assert_eq!(table.get(&Color::Blue), &33);
665    }
666
667    macro_rules! run_variants_test {
668        ($($variant:ident),+) => {{
669            #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
670            #[repr(u8)]
671            enum Test {
672                $($variant,)*
673            }
674
675            let map = EnumTable::<Test, &'static str, { Test::COUNT }>::new_with_fn(|t| match t {
676                $(Test::$variant => stringify!($variant),)*
677            });
678            $(
679                assert_eq!(map.get(&Test::$variant), &stringify!($variant));
680            )*
681        }};
682    }
683
684    #[test]
685    fn binary_search_correct_variants() {
686        run_variants_test!(A);
687        run_variants_test!(A, B);
688        run_variants_test!(A, B, C);
689        run_variants_test!(A, B, C, D);
690        run_variants_test!(A, B, C, D, E);
691    }
692}