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