enum_table/
lib.rs

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