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