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