Skip to main content

enum_table/
lib.rs

1#![doc = include_str!(concat!("../", core::env!("CARGO_PKG_README")))]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4#[cfg(test)]
5pub extern crate self as enum_table;
6
7use core::marker::PhantomData;
8
9#[cfg(feature = "derive")]
10pub use enum_table_derive::Enumable;
11
12pub mod builder;
13mod intrinsics;
14
15pub mod __private {
16    pub use crate::intrinsics::{sort_variants, variant_index_of};
17}
18
19mod impls;
20#[allow(unused_imports)]
21pub use impls::*;
22
23mod macros;
24
25/// A trait for enumerations that can be used with `EnumTable`.
26///
27/// This trait requires that the enumeration provides a static array of its variants
28/// and a constant representing the count of these variants.
29///
30/// # Safety
31///
32/// The implementations of this trait rely on the memory layout of the enum.
33/// It is strongly recommended to use a primitive representation (e.g., `#[repr(u8)]`)
34/// to ensure that the enum has no padding bytes and a stable layout.
35///
36/// **Note on Padding:** If the enum contains padding bytes (e.g., `#[repr(u8, align(2))]`),
37/// it will cause a **compile-time error** during constant evaluation, as Rust's
38/// constant evaluator does not allow reading uninitialized memory (padding).
39pub trait Enumable: Copy + 'static {
40    const VARIANTS: &'static [Self];
41    const COUNT: usize = Self::VARIANTS.len();
42
43    /// Returns the index of this variant in the sorted `VARIANTS` array.
44    ///
45    /// When derived via `#[derive(Enumable)]`, this is O(1) at runtime
46    /// (using compile-time-computed constants). The default implementation
47    /// falls back to O(log N) binary search for manual implementations.
48    fn variant_index(&self) -> usize {
49        intrinsics::binary_search_index::<Self>(self)
50    }
51}
52
53/// A table that associates each variant of an enumeration with a value.
54///
55/// `EnumTable` is a generic struct that uses an enumeration as keys and stores
56/// associated values. It provides efficient constant-time access (O(1))
57/// to the values based on the enumeration variant. This is particularly useful
58/// when you want to map enum variants to specific values without the overhead
59/// of a `HashMap`.
60///
61/// # Guarantees and Design
62///
63/// The core design principle of `EnumTable` is that an instance is guaranteed to hold a
64/// value for every variant of the enum `K`. This guarantee allows for a cleaner API
65/// than general-purpose map structures.
66///
67/// For example, the [`Self::get`] method returns `&V` directly. This is in contrast to
68/// [`std::collections::HashMap::get`], which returns an `Option<&V>` because a key may or may not be
69/// present. With `EnumTable`, the presence of all keys (variants) is a type-level
70/// invariant, eliminating the need for `unwrap()` or other `Option` handling.
71///
72/// If you need to handle cases where a value might not be present or will be set
73/// later, you can use `Option<V>` as the value type: `EnumTable<K, Option<V>, N>`.
74/// The struct provides convenient methods like [`Self::new_fill_with_none`] for this pattern.
75///
76/// # Type Parameters
77///
78/// * `K`: The enumeration type that implements the `Enumable` trait. This trait
79///   ensures that the enum provides a static array of its variants and a count
80///   of these variants.
81/// * `V`: The type of values to be associated with each enum variant.
82/// * `N`: The number of variants in the enum, which should match the length of
83///   the static array of variants provided by the `Enumable` trait.
84///
85/// # Examples
86///
87/// ```rust
88/// use enum_table::{EnumTable, Enumable};
89///
90/// #[derive(Enumable, Copy, Clone)]
91/// enum Color {
92///     Red,
93///     Green,
94///     Blue,
95/// }
96///
97/// // Create an EnumTable using the new_with_fn method
98/// let table = EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
99///     Color::Red => "Red",
100///     Color::Green => "Green",
101///     Color::Blue => "Blue",
102/// });
103///
104/// // Access values associated with enum variants
105/// assert_eq!(table.get(&Color::Red), &"Red");
106/// assert_eq!(table.get(&Color::Green), &"Green");
107/// assert_eq!(table.get(&Color::Blue), &"Blue");
108/// ```
109pub struct EnumTable<K: Enumable, V, const N: usize> {
110    table: [V; N],
111    _phantom: PhantomData<K>,
112}
113
114impl<K: Enumable, V, const N: usize> EnumTable<K, V, N> {
115    /// Creates a new `EnumTable` with the given table of variants and values.
116    /// Typically, you would use the [`crate::et`] macro or [`crate::builder::EnumTableBuilder`] to create an `EnumTable`.
117    pub(crate) const fn new(table: [V; N]) -> Self {
118        const {
119            assert!(
120                N == K::COUNT,
121                "EnumTable: N must equal K::COUNT. The const generic N does not match the number of enum variants."
122            );
123
124            // Ensure that the variants are sorted by their discriminants.
125            // This is a compile-time check to ensure that the variants are in the correct order.
126            if !intrinsics::is_sorted(K::VARIANTS) {
127                panic!(
128                    "Enumable: variants are not sorted by discriminant. Use `enum_table::Enumable` derive macro to ensure correct ordering."
129                );
130            }
131        }
132
133        Self {
134            table,
135            _phantom: PhantomData,
136        }
137    }
138
139    /// Creates a new `EnumTable` using a function to generate values for each variant.
140    ///
141    /// If you want to define it in a `const` context, use the [`crate::et`] macro instead.
142    ///
143    /// # Arguments
144    ///
145    /// * `f` - A function that takes a reference to an enumeration variant and returns
146    ///   a value to be associated with that variant.
147    pub fn new_with_fn(mut f: impl FnMut(&K) -> V) -> Self {
148        Self::new(core::array::from_fn(|i| f(&K::VARIANTS[i])))
149    }
150
151    /// Creates a new `EnumTable` using a function that returns a `Result` for each variant.
152    ///
153    /// This method applies the provided closure to each variant of the enum. If the closure
154    /// returns `Ok(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
155    /// If the closure returns `Err(e)` for any variant, the construction is aborted and
156    /// `Err((variant, e))` is returned, where `variant` is the enum variant that caused the error.
157    ///
158    /// # Arguments
159    ///
160    /// * `f` - A closure that takes a reference to an enum variant and returns a `Result<V, E>`.
161    ///
162    /// # Returns
163    ///
164    /// * `Ok(Self)` if all variants succeed.
165    /// * `Err((variant, e))` if any variant fails, containing the failing variant and the error.
166    pub fn try_new_with_fn<E>(mut f: impl FnMut(&K) -> Result<V, E>) -> Result<Self, (K, E)> {
167        let table = intrinsics::try_collect_array(|i| {
168            let variant = &K::VARIANTS[i];
169            f(variant).map_err(|e| (*variant, e))
170        })?;
171        Ok(Self::new(table))
172    }
173
174    /// Creates a new `EnumTable` using a function that returns an `Option` for each variant.
175    ///
176    /// This method applies the provided closure to each variant of the enum. If the closure
177    /// returns `Some(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
178    /// If the closure returns `None` for any variant, the construction is aborted and
179    /// `Err(variant)` is returned, where `variant` is the enum variant that caused the failure.
180    ///
181    /// # Arguments
182    ///
183    /// * `f` - A closure that takes a reference to an enum variant and returns an `Option<V>`.
184    ///
185    /// # Returns
186    ///
187    /// * `Ok(Self)` if all variants succeed.
188    /// * `Err(variant)` if any variant fails, containing the failing variant.
189    pub fn checked_new_with_fn(mut f: impl FnMut(&K) -> Option<V>) -> Result<Self, K> {
190        let table = intrinsics::try_collect_array(|i| {
191            let variant = &K::VARIANTS[i];
192            f(variant).ok_or(*variant)
193        })?;
194        Ok(Self::new(table))
195    }
196
197    /// Returns a reference to the value associated with the given enumeration variant.
198    ///
199    /// Uses O(1) lookup via [`Enumable::variant_index`].
200    ///
201    /// # Arguments
202    ///
203    /// * `variant` - A reference to an enumeration variant.
204    pub fn get(&self, variant: &K) -> &V {
205        &self.table[variant.variant_index()]
206    }
207
208    /// Returns a mutable reference to the value associated with the given enumeration variant.
209    ///
210    /// Uses O(1) lookup via [`Enumable::variant_index`].
211    ///
212    /// # Arguments
213    ///
214    /// * `variant` - A reference to an enumeration variant.
215    pub fn get_mut(&mut self, variant: &K) -> &mut V {
216        &mut self.table[variant.variant_index()]
217    }
218
219    /// Sets the value associated with the given enumeration variant.
220    ///
221    /// Uses O(1) lookup via [`Enumable::variant_index`].
222    ///
223    /// # Arguments
224    ///
225    /// * `variant` - A reference to an enumeration variant.
226    /// * `value` - The new value to associate with the variant.
227    ///
228    /// # Returns
229    ///
230    /// The old value associated with the variant.
231    pub fn set(&mut self, variant: &K, value: V) -> V {
232        core::mem::replace(&mut self.table[variant.variant_index()], value)
233    }
234
235    /// Returns a reference to the value associated with the given enumeration variant.
236    ///
237    /// This is a `const fn` that uses binary search (O(log N)).
238    /// For O(1) access, use [`Self::get`].
239    ///
240    /// # Arguments
241    ///
242    /// * `variant` - A reference to an enumeration variant.
243    pub const fn get_const(&self, variant: &K) -> &V {
244        let idx = intrinsics::binary_search_index::<K>(variant);
245        &self.table[idx]
246    }
247
248    /// Returns a mutable reference to the value associated with the given enumeration variant.
249    ///
250    /// This is a `const fn` that uses binary search (O(log N)).
251    /// For O(1) access, use [`Self::get_mut`].
252    ///
253    /// # Arguments
254    ///
255    /// * `variant` - A reference to an enumeration variant.
256    pub const fn get_mut_const(&mut self, variant: &K) -> &mut V {
257        let idx = intrinsics::binary_search_index::<K>(variant);
258        &mut self.table[idx]
259    }
260
261    /// Sets the value associated with the given enumeration variant.
262    ///
263    /// This is a `const fn` that uses binary search (O(log N)).
264    /// For O(1) access, use [`Self::set`].
265    ///
266    /// # Arguments
267    ///
268    /// * `variant` - A reference to an enumeration variant.
269    /// * `value` - The new value to associate with the variant.
270    ///
271    /// # Returns
272    ///
273    /// The old value associated with the variant.
274    pub const fn set_const(&mut self, variant: &K, value: V) -> V {
275        let idx = intrinsics::binary_search_index::<K>(variant);
276        core::mem::replace(&mut self.table[idx], value)
277    }
278
279    /// Returns the number of entries in the table (equal to the number of enum variants).
280    pub const fn len(&self) -> usize {
281        N
282    }
283
284    /// Returns `true` if the table has no entries (i.e., the enum has no variants).
285    pub const fn is_empty(&self) -> bool {
286        N == 0
287    }
288
289    /// Returns a reference to the underlying array of values.
290    ///
291    /// Values are ordered by the sorted discriminant of the enum variants.
292    pub const fn as_slice(&self) -> &[V] {
293        &self.table
294    }
295
296    /// Returns a mutable reference to the underlying array of values.
297    ///
298    /// Values are ordered by the sorted discriminant of the enum variants.
299    pub const fn as_mut_slice(&mut self) -> &mut [V] {
300        &mut self.table
301    }
302
303    /// Consumes the table and returns the underlying array of values.
304    ///
305    /// Values are ordered by the sorted discriminant of the enum variants.
306    pub fn into_array(self) -> [V; N] {
307        self.table
308    }
309
310    /// Combines two `EnumTable`s into a new one by applying a function to each pair of values.
311    ///
312    /// # Arguments
313    ///
314    /// * `other` - Another `EnumTable` with the same key type.
315    /// * `f` - A closure that takes two values and returns a new value.
316    ///
317    /// # Examples
318    ///
319    /// ```rust
320    /// use enum_table::{EnumTable, Enumable};
321    ///
322    /// #[derive(Enumable, Copy, Clone)]
323    /// enum Stat {
324    ///     Hp,
325    ///     Attack,
326    ///     Defense,
327    /// }
328    ///
329    /// let base = EnumTable::<Stat, i32, { Stat::COUNT }>::new_with_fn(|s| match s {
330    ///     Stat::Hp => 100,
331    ///     Stat::Attack => 50,
332    ///     Stat::Defense => 30,
333    /// });
334    /// let bonus = EnumTable::<Stat, i32, { Stat::COUNT }>::new_with_fn(|s| match s {
335    ///     Stat::Hp => 20,
336    ///     Stat::Attack => 10,
337    ///     Stat::Defense => 5,
338    /// });
339    ///
340    /// let total = base.zip(bonus, |a, b| a + b);
341    /// assert_eq!(total.get(&Stat::Hp), &120);
342    /// assert_eq!(total.get(&Stat::Attack), &60);
343    /// assert_eq!(total.get(&Stat::Defense), &35);
344    /// ```
345    pub fn zip<U, W>(
346        self,
347        other: EnumTable<K, U, N>,
348        mut f: impl FnMut(V, U) -> W,
349    ) -> EnumTable<K, W, N> {
350        let mut other_iter = other.table.into_iter();
351        EnumTable::new(self.table.map(|v| {
352            // SAFETY: both arrays have exactly N elements, and map calls this exactly N times
353            let u = unsafe { other_iter.next().unwrap_unchecked() };
354            f(v, u)
355        }))
356    }
357
358    /// Transforms all values in the table using the provided function.
359    ///
360    /// This method consumes the table and creates a new one with values
361    /// transformed by the given closure.
362    ///
363    /// # Arguments
364    ///
365    /// * `f` - A closure that takes an owned value and returns a new value.
366    ///
367    /// # Examples
368    ///
369    /// ```rust
370    /// use enum_table::{EnumTable, Enumable};
371    ///
372    /// #[derive(Enumable, Copy, Clone)]
373    /// enum Size {
374    ///     Small,
375    ///     Medium,
376    ///     Large,
377    /// }
378    ///
379    /// let table = EnumTable::<Size, i32, { Size::COUNT }>::new_with_fn(|size| match size {
380    ///     Size::Small => 1,
381    ///     Size::Medium => 2,
382    ///     Size::Large => 3,
383    /// });
384    ///
385    /// let doubled = table.map(|value| value * 2);
386    ///
387    /// assert_eq!(doubled.get(&Size::Small), &2);
388    /// assert_eq!(doubled.get(&Size::Medium), &4);
389    /// assert_eq!(doubled.get(&Size::Large), &6);
390    /// ```
391    pub fn map<U>(self, f: impl FnMut(V) -> U) -> EnumTable<K, U, N> {
392        EnumTable::new(self.table.map(f))
393    }
394
395    /// Transforms all values in the table using the provided function, with access to the key.
396    ///
397    /// This method consumes the table and creates a new one with values
398    /// transformed by the given closure, which receives both the key and the value.
399    ///
400    /// # Arguments
401    ///
402    /// * `f` - A closure that takes a key reference and an owned value, and returns a new value.
403    pub fn map_with_key<U>(self, mut f: impl FnMut(&K, V) -> U) -> EnumTable<K, U, N> {
404        let mut i = 0;
405        EnumTable::new(self.table.map(|value| {
406            let key = &K::VARIANTS[i];
407            i += 1;
408            f(key, value)
409        }))
410    }
411
412    /// Transforms all values in the table in-place using the provided function.
413    ///
414    /// # Arguments
415    ///
416    /// * `f` - A closure that takes a mutable reference to a value and modifies it.
417    ///
418    /// # Examples
419    ///
420    /// ```rust
421    /// use enum_table::{EnumTable, Enumable};
422    ///
423    /// #[derive(Enumable, Copy, Clone)]
424    /// enum Level {
425    ///     Low,
426    ///     Medium,
427    ///     High,
428    /// }
429    ///
430    /// let mut table = EnumTable::<Level, i32, { Level::COUNT }>::new_with_fn(|level| match level {
431    ///     Level::Low => 10,
432    ///     Level::Medium => 20,
433    ///     Level::High => 30,
434    /// });
435    ///
436    /// table.map_mut(|value| *value += 5);
437    ///
438    /// assert_eq!(table.get(&Level::Low), &15);
439    /// assert_eq!(table.get(&Level::Medium), &25);
440    /// assert_eq!(table.get(&Level::High), &35);
441    /// ```
442    pub fn map_mut(&mut self, f: impl FnMut(&mut V)) {
443        self.table.iter_mut().for_each(f);
444    }
445
446    /// Transforms all values in the table in-place using the provided function, with access to the key.
447    ///
448    /// # Arguments
449    ///
450    /// * `f` - A closure that takes a key reference and a mutable reference to a value, and modifies it.
451    pub fn map_mut_with_key(&mut self, mut f: impl FnMut(&K, &mut V)) {
452        self.table.iter_mut().enumerate().for_each(|(i, value)| {
453            f(&K::VARIANTS[i], value);
454        });
455    }
456}
457
458impl<K: Enumable, V, const N: usize> EnumTable<K, Option<V>, N> {
459    /// Creates a new `EnumTable` with `None` values for each variant.
460    pub const fn new_fill_with_none() -> Self {
461        Self::new([const { None }; N])
462    }
463
464    /// Clears the table, setting each value to `None`.
465    pub fn clear_to_none(&mut self) {
466        for value in &mut self.table {
467            *value = None;
468        }
469    }
470
471    /// Removes and returns the value associated with the given enumeration variant,
472    /// leaving `None` in its place.
473    ///
474    /// Uses O(1) lookup via [`Enumable::variant_index`].
475    ///
476    /// # Arguments
477    ///
478    /// * `variant` - A reference to an enumeration variant.
479    ///
480    /// # Returns
481    ///
482    /// The previous value, or `None` if the slot was already empty.
483    pub fn remove(&mut self, variant: &K) -> Option<V> {
484        self.table[variant.variant_index()].take()
485    }
486
487    /// Removes and returns the value associated with the given enumeration variant,
488    /// leaving `None` in its place.
489    ///
490    /// This is a `const fn` that uses binary search (O(log N)).
491    /// For O(1) access, use [`Self::remove`].
492    ///
493    /// # Arguments
494    ///
495    /// * `variant` - A reference to an enumeration variant.
496    ///
497    /// # Returns
498    ///
499    /// The previous value, or `None` if the slot was already empty.
500    pub const fn remove_const(&mut self, variant: &K) -> Option<V> {
501        let idx = intrinsics::binary_search_index::<K>(variant);
502        self.table[idx].take()
503    }
504}
505
506impl<K: Enumable, V: Copy, const N: usize> EnumTable<K, V, N> {
507    /// Creates a new `EnumTable` with the same copied value for each variant.
508    ///
509    /// This method initializes the table with the same value for each
510    /// variant of the enumeration. The value must implement `Copy`.
511    ///
512    /// # Arguments
513    ///
514    /// * `value` - The value to copy for each enum variant.
515    ///
516    /// # Examples
517    ///
518    /// ```rust
519    /// use enum_table::{EnumTable, Enumable};
520    ///
521    /// #[derive(Enumable, Copy, Clone)]
522    /// enum Status {
523    ///     Active,
524    ///     Inactive,
525    ///     Pending,
526    /// }
527    ///
528    /// let table = EnumTable::<Status, i32, { Status::COUNT }>::new_fill_with_copy(42);
529    ///
530    /// assert_eq!(table.get(&Status::Active), &42);
531    /// assert_eq!(table.get(&Status::Inactive), &42);
532    /// assert_eq!(table.get(&Status::Pending), &42);
533    /// ```
534    pub const fn new_fill_with_copy(value: V) -> Self {
535        Self::new([value; N])
536    }
537}
538
539impl<K: Enumable, V: Default, const N: usize> EnumTable<K, V, N> {
540    /// Creates a new `EnumTable` with default values for each variant.
541    ///
542    /// This method initializes the table with the default value of type `V` for each
543    /// variant of the enumeration.
544    pub fn new_fill_with_default() -> Self {
545        Self::new(core::array::from_fn(|_| V::default()))
546    }
547
548    /// Clears the table, setting each value to its default.
549    pub fn clear_to_default(&mut self) {
550        self.table.fill_with(V::default);
551    }
552}
553
554#[cfg(test)]
555mod tests {
556    use super::*;
557
558    #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
559    enum Color {
560        Red = 33,
561        Green = 11,
562        Blue = 222,
563    }
564
565    const TABLES: EnumTable<Color, &'static str, { Color::COUNT }> =
566        crate::et!(Color, &'static str, |color| match color {
567            Color::Red => "Red",
568            Color::Green => "Green",
569            Color::Blue => "Blue",
570        });
571
572    #[test]
573    fn new_with_fn() {
574        let table =
575            EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
576                Color::Red => "Red",
577                Color::Green => "Green",
578                Color::Blue => "Blue",
579            });
580
581        assert_eq!(table.get(&Color::Red), &"Red");
582        assert_eq!(table.get(&Color::Green), &"Green");
583        assert_eq!(table.get(&Color::Blue), &"Blue");
584    }
585
586    #[test]
587    fn try_new_with_fn() {
588        let table =
589            EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
590                |color| match color {
591                    Color::Red => Ok::<&'static str, core::convert::Infallible>("Red"),
592                    Color::Green => Ok("Green"),
593                    Color::Blue => Ok("Blue"),
594                },
595            );
596
597        assert!(table.is_ok());
598        let table = table.unwrap();
599
600        assert_eq!(table.get(&Color::Red), &"Red");
601        assert_eq!(table.get(&Color::Green), &"Green");
602        assert_eq!(table.get(&Color::Blue), &"Blue");
603
604        let error_table = EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
605            |color| match color {
606                Color::Red => Ok("Red"),
607                Color::Green => Err("Error on Green"),
608                Color::Blue => Ok("Blue"),
609            },
610        );
611
612        assert!(error_table.is_err());
613        let (variant, error) = error_table.unwrap_err();
614
615        assert_eq!(variant, Color::Green);
616        assert_eq!(error, "Error on Green");
617    }
618
619    #[test]
620    fn checked_new_with_fn() {
621        let table =
622            EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
623                match color {
624                    Color::Red => Some("Red"),
625                    Color::Green => Some("Green"),
626                    Color::Blue => Some("Blue"),
627                }
628            });
629
630        assert!(table.is_ok());
631        let table = table.unwrap();
632
633        assert_eq!(table.get(&Color::Red), &"Red");
634        assert_eq!(table.get(&Color::Green), &"Green");
635        assert_eq!(table.get(&Color::Blue), &"Blue");
636
637        let error_table =
638            EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
639                match color {
640                    Color::Red => Some("Red"),
641                    Color::Green => None,
642                    Color::Blue => Some("Blue"),
643                }
644            });
645
646        assert!(error_table.is_err());
647        let variant = error_table.unwrap_err();
648
649        assert_eq!(variant, Color::Green);
650    }
651
652    #[test]
653    fn get() {
654        assert_eq!(TABLES.get(&Color::Red), &"Red");
655        assert_eq!(TABLES.get(&Color::Green), &"Green");
656        assert_eq!(TABLES.get(&Color::Blue), &"Blue");
657    }
658
659    #[test]
660    fn get_mut() {
661        let mut table = TABLES;
662        assert_eq!(table.get_mut(&Color::Red), &mut "Red");
663        assert_eq!(table.get_mut(&Color::Green), &mut "Green");
664        assert_eq!(table.get_mut(&Color::Blue), &mut "Blue");
665
666        *table.get_mut(&Color::Red) = "Changed Red";
667        *table.get_mut(&Color::Green) = "Changed Green";
668        *table.get_mut(&Color::Blue) = "Changed Blue";
669
670        assert_eq!(table.get(&Color::Red), &"Changed Red");
671        assert_eq!(table.get(&Color::Green), &"Changed Green");
672        assert_eq!(table.get(&Color::Blue), &"Changed Blue");
673    }
674
675    #[test]
676    fn set() {
677        let mut table = TABLES;
678        assert_eq!(table.set(&Color::Red, "New Red"), "Red");
679        assert_eq!(table.set(&Color::Green, "New Green"), "Green");
680        assert_eq!(table.set(&Color::Blue, "New Blue"), "Blue");
681
682        assert_eq!(table.get(&Color::Red), &"New Red");
683        assert_eq!(table.get(&Color::Green), &"New Green");
684        assert_eq!(table.get(&Color::Blue), &"New Blue");
685    }
686
687    #[test]
688    fn keys() {
689        let keys: Vec<_> = TABLES.keys().collect();
690        assert_eq!(keys, vec![&Color::Green, &Color::Red, &Color::Blue]);
691    }
692
693    #[test]
694    fn values() {
695        let values: Vec<_> = TABLES.values().collect();
696        assert_eq!(values, vec![&"Green", &"Red", &"Blue"]);
697    }
698
699    #[test]
700    fn iter() {
701        let iter: Vec<_> = TABLES.iter().collect();
702        assert_eq!(
703            iter,
704            vec![
705                (&Color::Green, &"Green"),
706                (&Color::Red, &"Red"),
707                (&Color::Blue, &"Blue")
708            ]
709        );
710    }
711
712    #[test]
713    fn iter_mut() {
714        let mut table = TABLES;
715        for (key, value) in table.iter_mut() {
716            *value = match key {
717                Color::Red => "Changed Red",
718                Color::Green => "Changed Green",
719                Color::Blue => "Changed Blue",
720            };
721        }
722        let iter: Vec<_> = table.iter().collect();
723        assert_eq!(
724            iter,
725            vec![
726                (&Color::Green, &"Changed Green"),
727                (&Color::Red, &"Changed Red"),
728                (&Color::Blue, &"Changed Blue")
729            ]
730        );
731    }
732
733    #[test]
734    fn map() {
735        let table = EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
736            Color::Red => 1,
737            Color::Green => 2,
738            Color::Blue => 3,
739        });
740
741        let doubled = table.map(|value| value * 2);
742
743        assert_eq!(doubled.get(&Color::Red), &2);
744        assert_eq!(doubled.get(&Color::Green), &4);
745        assert_eq!(doubled.get(&Color::Blue), &6);
746    }
747
748    #[test]
749    fn map_with_key() {
750        let table = EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
751            Color::Red => 1,
752            Color::Green => 2,
753            Color::Blue => 3,
754        });
755
756        let mapped = table.map_with_key(|key, value| match key {
757            Color::Red => value + 10,   // 1 + 10 = 11
758            Color::Green => value + 20, // 2 + 20 = 22
759            Color::Blue => value + 30,  // 3 + 30 = 33
760        });
761
762        // Note: The order in the underlying table is based on discriminant value (Green, Red, Blue)
763        assert_eq!(mapped.get(&Color::Red), &11);
764        assert_eq!(mapped.get(&Color::Green), &22);
765        assert_eq!(mapped.get(&Color::Blue), &33);
766    }
767
768    #[test]
769    fn map_mut() {
770        let mut table =
771            EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
772                Color::Red => 10,
773                Color::Green => 20,
774                Color::Blue => 30,
775            });
776
777        table.map_mut(|value| *value += 5);
778
779        assert_eq!(table.get(&Color::Red), &15);
780        assert_eq!(table.get(&Color::Green), &25);
781        assert_eq!(table.get(&Color::Blue), &35);
782    }
783
784    #[test]
785    fn map_mut_with_key() {
786        let mut table =
787            EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
788                Color::Red => 10,
789                Color::Green => 20,
790                Color::Blue => 30,
791            });
792
793        table.map_mut_with_key(|key, value| {
794            *value += match key {
795                Color::Red => 1,   // 10 + 1 = 11
796                Color::Green => 2, // 20 + 2 = 22
797                Color::Blue => 3,  // 30 + 3 = 33
798            }
799        });
800
801        assert_eq!(table.get(&Color::Red), &11);
802        assert_eq!(table.get(&Color::Green), &22);
803        assert_eq!(table.get(&Color::Blue), &33);
804    }
805
806    macro_rules! run_variants_test {
807        ($($variant:ident),+) => {{
808            #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
809            #[repr(u8)]
810            enum Test {
811                $($variant,)*
812            }
813
814            let map = EnumTable::<Test, &'static str, { Test::COUNT }>::new_with_fn(|t| match t {
815                $(Test::$variant => stringify!($variant),)*
816            });
817            $(
818                assert_eq!(map.get(&Test::$variant), &stringify!($variant));
819            )*
820        }};
821    }
822
823    #[test]
824    fn binary_search_correct_variants() {
825        run_variants_test!(A);
826        run_variants_test!(A, B);
827        run_variants_test!(A, B, C);
828        run_variants_test!(A, B, C, D);
829        run_variants_test!(A, B, C, D, E);
830    }
831
832    #[test]
833    fn variant_index() {
834        // Color discriminants: Green=11, Red=33, Blue=222
835        // Sorted order: Green(0), Red(1), Blue(2)
836        assert_eq!(Color::Green.variant_index(), 0);
837        assert_eq!(Color::Red.variant_index(), 1);
838        assert_eq!(Color::Blue.variant_index(), 2);
839    }
840
841    #[test]
842    fn get_const() {
843        const RED: &str = TABLES.get_const(&Color::Red);
844        const GREEN: &str = TABLES.get_const(&Color::Green);
845        const BLUE: &str = TABLES.get_const(&Color::Blue);
846
847        assert_eq!(RED, "Red");
848        assert_eq!(GREEN, "Green");
849        assert_eq!(BLUE, "Blue");
850    }
851
852    #[test]
853    fn set_const() {
854        const fn make_table() -> EnumTable<Color, &'static str, { Color::COUNT }> {
855            let mut table = TABLES;
856            table.set_const(&Color::Red, "New Red");
857            table
858        }
859        const TABLE: EnumTable<Color, &'static str, { Color::COUNT }> = make_table();
860        assert_eq!(TABLE.get_const(&Color::Red), &"New Red");
861        assert_eq!(TABLE.get_const(&Color::Green), &"Green");
862    }
863
864    #[test]
865    fn get_mut_const() {
866        const fn make_table() -> EnumTable<Color, &'static str, { Color::COUNT }> {
867            let mut table = TABLES;
868            *table.get_mut_const(&Color::Green) = "Changed Green";
869            table
870        }
871        const TABLE: EnumTable<Color, &'static str, { Color::COUNT }> = make_table();
872        assert_eq!(TABLE.get_const(&Color::Green), &"Changed Green");
873    }
874
875    #[test]
876    fn remove_option() {
877        let mut table =
878            EnumTable::<Color, Option<i32>, { Color::COUNT }>::new_with_fn(|color| match color {
879                Color::Red => Some(1),
880                Color::Green => Some(2),
881                Color::Blue => None,
882            });
883
884        assert_eq!(table.remove(&Color::Red), Some(1));
885        assert_eq!(table.get(&Color::Red), &None);
886
887        assert_eq!(table.remove(&Color::Blue), None);
888        assert_eq!(table.get(&Color::Blue), &None);
889    }
890
891    #[test]
892    fn remove_const_option() {
893        const fn make_table() -> EnumTable<Color, Option<i32>, { Color::COUNT }> {
894            let mut table = EnumTable::new_fill_with_none();
895            table.set_const(&Color::Red, Some(42));
896            table.set_const(&Color::Green, Some(99));
897            table
898        }
899
900        let mut table = make_table();
901        assert_eq!(table.remove_const(&Color::Red), Some(42));
902        assert_eq!(table.get(&Color::Red), &None);
903    }
904
905    #[test]
906    fn as_slice() {
907        let slice = TABLES.as_slice();
908        assert_eq!(slice.len(), 3);
909        // Values are in sorted discriminant order: Green(11), Red(33), Blue(222)
910        assert_eq!(slice[0], "Green");
911        assert_eq!(slice[1], "Red");
912        assert_eq!(slice[2], "Blue");
913    }
914
915    #[test]
916    fn as_mut_slice() {
917        let mut table = TABLES;
918        let slice = table.as_mut_slice();
919        slice[0] = "Changed Green";
920        assert_eq!(table.get(&Color::Green), &"Changed Green");
921    }
922
923    #[test]
924    fn into_array() {
925        let arr = TABLES.into_array();
926        assert_eq!(arr, ["Green", "Red", "Blue"]);
927    }
928
929    #[test]
930    fn zip() {
931        let a = EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|c| match c {
932            Color::Red => -10,
933            Color::Green => -20,
934            Color::Blue => -30,
935        });
936        let b = EnumTable::<Color, u32, { Color::COUNT }>::new_with_fn(|c| match c {
937            Color::Red => 1,
938            Color::Green => 2,
939            Color::Blue => 3,
940        });
941
942        let sum = a.zip(b, |x, y| (x + y as i32) as i8);
943        assert_eq!(sum.get(&Color::Red), &-9);
944        assert_eq!(sum.get(&Color::Green), &-18);
945        assert_eq!(sum.get(&Color::Blue), &-27);
946    }
947}