enum_table/
lib.rs

1#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
2
3#[cfg(feature = "derive")]
4pub use enum_table_derive::Enumable;
5
6pub mod builder;
7mod impls;
8mod macros;
9
10use dev_macros::*;
11
12/// A trait for enumerations that can be used with `EnumTable`.
13///
14/// This trait requires that the enumeration provides a static array of its variants
15/// and a constant representing the count of these variants.
16pub trait Enumable: Sized + 'static {
17    const VARIANTS: &'static [Self];
18    const COUNT: usize = Self::VARIANTS.len();
19}
20
21const fn to_usize<T: Copy>(t: T) -> usize {
22    #[inline(always)]
23    const fn cast<U>(t: &impl Sized) -> &U {
24        // SAFETY: This is safe because we ensure that the type T is a valid representation
25        unsafe { std::mem::transmute(t) }
26    }
27
28    let t = &t;
29
30    match const { core::mem::size_of::<T>() } {
31        1 => *cast::<u8>(t) as usize,
32        2 => *cast::<u16>(t) as usize,
33        4 => *cast::<u32>(t) as usize,
34        #[cfg(target_pointer_width = "64")]
35        8 => *cast::<u64>(t) as usize,
36        #[cfg(target_pointer_width = "32")]
37        8 => panic!("Unsupported size: 64-bit value found on a 32-bit architecture"),
38        _ => panic!("Values larger than u64 are not supported"),
39    }
40}
41
42const fn from_usize<T>(u: &usize) -> &T {
43    unsafe {
44        // SAFETY: This is safe because we ensure that the usize is derived from a valid T
45        std::mem::transmute::<&usize, &T>(u)
46    }
47}
48
49/// A table that associates each variant of an enumeration with a value.
50///
51/// `EnumTable` is a generic struct that uses an enumeration as keys and stores
52/// associated values. It provides constant-time access to the values based on
53/// the enumeration variant. This is particularly useful when you want to map
54/// enum variants to specific values without the overhead of a `HashMap`.
55///
56/// # Type Parameters
57///
58/// * `K`: The enumeration type that implements the `Enumable` trait. This trait
59///   ensures that the enum provides a static array of its variants and a count
60///   of these variants.
61/// * `V`: The type of values to be associated with each enum variant.
62/// * `N`: The number of variants in the enum, which should match the length of
63///   the static array of variants provided by the `Enumable` trait.
64///
65/// # Note
66/// The `new` method allows for the creation of an `EnumTable` in `const` contexts,
67/// but it does not perform compile-time checks. For enhanced compile-time safety
68/// and convenience, it is advisable to use the [`crate::et`] macro or
69/// [`crate::builder::EnumTableBuilder`], which provide these checks.
70///
71/// # Examples
72///
73/// ```rust
74/// use enum_table::{EnumTable, Enumable};
75///
76/// #[derive(Enumable)]
77/// enum Color {
78///     Red,
79///     Green,
80///     Blue,
81/// }
82///
83/// // Create an EnumTable using the new_with_fn method
84/// let table = EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
85///     Color::Red => "Red",
86///     Color::Green => "Green",
87///     Color::Blue => "Blue",
88/// });
89///
90/// // Access values associated with enum variants
91/// assert_eq!(table.get(&Color::Red), &"Red");
92/// assert_eq!(table.get(&Color::Green), &"Green");
93/// assert_eq!(table.get(&Color::Blue), &"Blue");
94/// ```
95pub struct EnumTable<K: Enumable, V, const N: usize> {
96    table: [(usize, V); N],
97    _phantom: core::marker::PhantomData<K>,
98}
99
100impl<K: Enumable, V, const N: usize> EnumTable<K, V, N> {
101    /// Creates a new `EnumTable` with the given table of discriminants and values.
102    /// Typically, you would use the [`crate::et`] macro or the [`crate::builder::EnumTableBuilder`] instead.
103    ///
104    ///
105    /// # Arguments
106    ///
107    /// * `table` - An array of tuples where each tuple contains a discriminant of
108    ///   an enumeration variant and its associated value.
109    pub const fn new(table: [(usize, V); N]) -> Self {
110        Self {
111            table,
112            _phantom: core::marker::PhantomData,
113        }
114    }
115
116    /// Create a new EnumTable with a function that takes a variant and returns a value.
117    /// If you want to define it in const, use [`crate::et`] macro
118    /// Creates a new `EnumTable` using a function to generate values for each variant.
119    ///
120    /// # Arguments
121    ///
122    /// * `f` - A function that takes a reference to an enumeration variant and returns
123    ///   a value to be associated with that variant.
124    pub fn new_with_fn(mut f: impl FnMut(&K) -> V) -> Self {
125        let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
126
127        for variant in K::VARIANTS {
128            builder.push(variant, f(variant));
129        }
130
131        builder.build_to()
132    }
133
134    /// Creates a new `EnumTable` using a function that returns a `Result` for each variant.
135    ///
136    /// This method applies the provided closure to each variant of the enum. If the closure
137    /// returns `Ok(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
138    /// If the closure returns `Err(e)` for any variant, the construction is aborted and
139    /// `Err((variant, e))` is returned, where `variant` is the enum variant that caused the error.
140    ///
141    /// # Arguments
142    ///
143    /// * `f` - A closure that takes a reference to an enum variant and returns a `Result<V, E>`.
144    ///
145    /// # Returns
146    ///
147    /// * `Ok(Self)` if all variants succeed.
148    /// * `Err((variant, e))` if any variant fails, containing the failing variant and the error.
149    pub fn try_new_with_fn<E>(
150        mut f: impl FnMut(&K) -> Result<V, E>,
151    ) -> Result<Self, (&'static K, E)> {
152        let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
153
154        for variant in K::VARIANTS {
155            match f(variant) {
156                Ok(value) => builder.push(variant, value),
157                Err(e) => return Err((variant, e)),
158            }
159        }
160
161        Ok(builder.build_to())
162    }
163
164    /// Creates a new `EnumTable` using a function that returns an `Option` for each variant.
165    ///
166    /// This method applies the provided closure to each variant of the enum. If the closure
167    /// returns `Some(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
168    /// If the closure returns `None` for any variant, the construction is aborted and
169    /// `Err(variant)` is returned, where `variant` is the enum variant that caused the failure.
170    ///
171    /// # Arguments
172    ///
173    /// * `f` - A closure that takes a reference to an enum variant and returns an `Option<V>`.
174    ///
175    /// # Returns
176    ///
177    /// * `Ok(Self)` if all variants succeed.
178    /// * `Err(variant)` if any variant fails, containing the failing variant.
179    pub fn checked_new_with_fn(mut f: impl FnMut(&K) -> Option<V>) -> Result<Self, &'static K> {
180        let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
181
182        for variant in K::VARIANTS {
183            if let Some(value) = f(variant) {
184                builder.push(variant, value);
185            } else {
186                return Err(variant);
187            }
188        }
189
190        Ok(builder.build_to())
191    }
192
193    /// Returns a 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(&self, variant: &K) -> &V {
199        use_variant_value!(self, variant, i, {
200            return &self.table[i].1;
201        });
202    }
203
204    /// Returns a mutable reference to the value associated with the given enumeration variant.
205    ///
206    /// # Arguments
207    ///
208    /// * `variant` - A reference to an enumeration variant.
209    pub const fn get_mut(&mut self, variant: &K) -> &mut V {
210        use_variant_value!(self, variant, i, {
211            return &mut self.table[i].1;
212        });
213    }
214
215    /// Sets the value associated with the given enumeration variant.
216    ///
217    /// # Arguments
218    ///
219    /// * `variant` - A reference to an enumeration variant.
220    /// * `value` - The new value to associate with the variant.
221    /// # Returns
222    /// The old value associated with the variant.
223    pub const fn set(&mut self, variant: &K, value: V) -> V {
224        use_variant_value!(self, variant, i, {
225            return core::mem::replace(&mut self.table[i].1, value);
226        });
227    }
228
229    /// Returns the number of generic N
230    pub const fn len(&self) -> usize {
231        N
232    }
233
234    /// Returns `false` since the table is never empty.
235    pub const fn is_empty(&self) -> bool {
236        false
237    }
238
239    /// Returns an iterator over references to the keys in the table.
240    pub fn keys(&self) -> impl Iterator<Item = &K> {
241        self.table
242            .iter()
243            .map(|(discriminant, _)| from_usize(discriminant))
244    }
245
246    /// Returns an iterator over references to the values in the table.
247    pub fn values(&self) -> impl Iterator<Item = &V> {
248        self.table.iter().map(|(_, value)| value)
249    }
250
251    /// Returns an iterator over mutable references to the values in the table.
252    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
253        self.table.iter_mut().map(|(_, value)| value)
254    }
255
256    /// Returns an iterator over mutable references to the values in the table.
257    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
258        self.table
259            .iter()
260            .map(|(discriminant, value)| (from_usize(discriminant), value))
261    }
262
263    /// Returns an iterator over mutable references to the values in the table.
264    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
265        self.table
266            .iter_mut()
267            .map(|(discriminant, value)| (from_usize(discriminant), value))
268    }
269}
270
271mod dev_macros {
272    macro_rules! use_variant_value {
273        ($self:ident, $variant:ident, $i:ident,{$($tt:tt)+}) => {
274            let discriminant = to_usize(core::mem::discriminant($variant));
275
276            let mut $i = 0;
277            while $i < $self.table.len() {
278                if $self.table[$i].0 == discriminant {
279                    $($tt)+
280                }
281                $i += 1;
282            }
283            unreachable!();
284        };
285    }
286
287    pub(super) use use_variant_value;
288}
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293
294    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
295    enum Color {
296        Red,
297        Green,
298        Blue,
299    }
300
301    impl Enumable for Color {
302        const VARIANTS: &'static [Self] = &[Color::Red, Color::Green, Color::Blue];
303    }
304
305    const TABLES: EnumTable<Color, &'static str, { Color::COUNT }> =
306        crate::et!(Color, &'static str, |color| match color {
307            Color::Red => "Red",
308            Color::Green => "Green",
309            Color::Blue => "Blue",
310        });
311
312    #[test]
313    fn new_with_fn() {
314        let table =
315            EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
316                Color::Red => "Red",
317                Color::Green => "Green",
318                Color::Blue => "Blue",
319            });
320
321        assert_eq!(table.get(&Color::Red), &"Red");
322        assert_eq!(table.get(&Color::Green), &"Green");
323        assert_eq!(table.get(&Color::Blue), &"Blue");
324    }
325
326    #[test]
327    fn try_new_with_fn() {
328        let table =
329            EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
330                |color| match color {
331                    Color::Red => Ok::<&'static str, std::convert::Infallible>("Red"),
332                    Color::Green => Ok("Green"),
333                    Color::Blue => Ok("Blue"),
334                },
335            );
336
337        assert!(table.is_ok());
338        let table = table.unwrap();
339
340        assert_eq!(table.get(&Color::Red), &"Red");
341        assert_eq!(table.get(&Color::Green), &"Green");
342        assert_eq!(table.get(&Color::Blue), &"Blue");
343
344        let error_table = EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
345            |color| match color {
346                Color::Red => Ok("Red"),
347                Color::Green => Err("Error on Green"),
348                Color::Blue => Ok("Blue"),
349            },
350        );
351
352        assert!(error_table.is_err());
353        let (variant, error) = error_table.unwrap_err();
354
355        assert_eq!(variant, &Color::Green);
356        assert_eq!(error, "Error on Green");
357    }
358
359    #[test]
360    fn checked_new_with_fn() {
361        let table =
362            EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
363                match color {
364                    Color::Red => Some("Red"),
365                    Color::Green => Some("Green"),
366                    Color::Blue => Some("Blue"),
367                }
368            });
369
370        assert!(table.is_ok());
371        let table = table.unwrap();
372
373        assert_eq!(table.get(&Color::Red), &"Red");
374        assert_eq!(table.get(&Color::Green), &"Green");
375        assert_eq!(table.get(&Color::Blue), &"Blue");
376
377        let error_table =
378            EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
379                match color {
380                    Color::Red => Some("Red"),
381                    Color::Green => None,
382                    Color::Blue => Some("Blue"),
383                }
384            });
385
386        assert!(error_table.is_err());
387        let variant = error_table.unwrap_err();
388
389        assert_eq!(variant, &Color::Green);
390    }
391
392    #[test]
393    fn get() {
394        assert_eq!(TABLES.get(&Color::Red), &"Red");
395        assert_eq!(TABLES.get(&Color::Green), &"Green");
396        assert_eq!(TABLES.get(&Color::Blue), &"Blue");
397    }
398
399    #[test]
400    fn get_mut() {
401        let mut table = TABLES;
402        assert_eq!(table.get_mut(&Color::Red), &mut "Red");
403        assert_eq!(table.get_mut(&Color::Green), &mut "Green");
404        assert_eq!(table.get_mut(&Color::Blue), &mut "Blue");
405
406        *table.get_mut(&Color::Red) = "Changed Red";
407        *table.get_mut(&Color::Green) = "Changed Green";
408        *table.get_mut(&Color::Blue) = "Changed Blue";
409
410        assert_eq!(table.get(&Color::Red), &"Changed Red");
411        assert_eq!(table.get(&Color::Green), &"Changed Green");
412        assert_eq!(table.get(&Color::Blue), &"Changed Blue");
413    }
414
415    #[test]
416    fn set() {
417        let mut table = TABLES;
418        assert_eq!(table.set(&Color::Red, "New Red"), "Red");
419        assert_eq!(table.set(&Color::Green, "New Green"), "Green");
420        assert_eq!(table.set(&Color::Blue, "New Blue"), "Blue");
421
422        assert_eq!(table.get(&Color::Red), &"New Red");
423        assert_eq!(table.get(&Color::Green), &"New Green");
424        assert_eq!(table.get(&Color::Blue), &"New Blue");
425    }
426
427    #[test]
428    fn keys() {
429        let keys: Vec<_> = TABLES.keys().collect();
430        assert_eq!(keys, vec![&Color::Red, &Color::Green, &Color::Blue]);
431    }
432
433    #[test]
434    fn values() {
435        let values: Vec<_> = TABLES.values().collect();
436        assert_eq!(values, vec![&"Red", &"Green", &"Blue"]);
437    }
438
439    #[test]
440    fn iter() {
441        let iter: Vec<_> = TABLES.iter().collect();
442        assert_eq!(
443            iter,
444            vec![
445                (&Color::Red, &"Red"),
446                (&Color::Green, &"Green"),
447                (&Color::Blue, &"Blue")
448            ]
449        );
450    }
451
452    #[test]
453    fn iter_mut() {
454        let mut table = TABLES;
455        for (key, value) in table.iter_mut() {
456            *value = match key {
457                Color::Red => "Changed Red",
458                Color::Green => "Changed Green",
459                Color::Blue => "Changed Blue",
460            };
461        }
462        let iter: Vec<_> = table.iter().collect();
463        assert_eq!(
464            iter,
465            vec![
466                (&Color::Red, &"Changed Red"),
467                (&Color::Green, &"Changed Green"),
468                (&Color::Blue, &"Changed Blue")
469            ]
470        );
471    }
472}