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 core::mem::Discriminant;
11use dev_macros::*;
12
13/// A trait for enumerations that can be used with `EnumTable`.
14///
15/// This trait requires that the enumeration provides a static array of its variants
16/// and a constant representing the count of these variants.
17pub trait Enumable: Sized + 'static {
18    const VARIANTS: &'static [Self];
19    const COUNT: usize = Self::VARIANTS.len();
20}
21
22const fn to_usize<T>(t: T) -> usize {
23    #[inline(always)]
24    const fn cast<T, U>(t: T) -> U {
25        use core::mem::ManuallyDrop;
26        unsafe { core::mem::transmute_copy::<ManuallyDrop<T>, U>(&ManuallyDrop::new(t)) }
27    }
28
29    match const { core::mem::size_of::<T>() } {
30        1 => cast::<T, u8>(t) as usize,
31        2 => cast::<T, u16>(t) as usize,
32        4 => cast::<T, u32>(t) as usize,
33        8 => cast::<T, u64>(t) as usize,
34        _ => panic!("Unsupported size"),
35    }
36}
37
38/// A table that associates each variant of an enumeration with a value.
39///
40/// `EnumTable` is a generic struct that uses an enumeration as keys and stores
41/// associated values. It provides constant-time access to the values based on
42/// the enumeration variant.
43#[derive(Debug, Clone, Copy)]
44pub struct EnumTable<K: Enumable, V, const N: usize> {
45    table: [(Discriminant<K>, V); N],
46}
47
48impl<K: Enumable, V, const N: usize> EnumTable<K, V, N> {
49    /// Creates a new `EnumTable` with the given table of discriminants and values.
50    ///
51    /// # Arguments
52    ///
53    /// * `table` - An array of tuples where each tuple contains a discriminant of
54    ///   an enumeration variant and its associated value.
55    pub const fn new(table: [(Discriminant<K>, V); N]) -> Self {
56        Self { table }
57    }
58
59    /// Create a new EnumTable with a function that takes a variant and returns a value.
60    /// If you want to define it in const, use [`crate::et`] macro
61    /// Creates a new `EnumTable` using a function to generate values for each variant.
62    ///
63    /// # Arguments
64    ///
65    /// * `f` - A function that takes a reference to an enumeration variant and returns
66    ///   a value to be associated with that variant.
67    pub fn new_with_fn(mut f: impl FnMut(&K) -> V) -> Self {
68        let table = core::array::from_fn(|i| {
69            let k = &K::VARIANTS[i];
70            (core::mem::discriminant(k), f(k))
71        });
72
73        Self { table }
74    }
75
76    /// Returns a reference to the value associated with the given enumeration variant.
77    ///
78    /// # Arguments
79    ///
80    /// * `variant` - A reference to an enumeration variant.
81    pub const fn get(&self, variant: &K) -> &V {
82        use_variant_value!(self, variant, i, {
83            return &self.table[i].1;
84        });
85    }
86
87    /// Returns a mutable reference to the value associated with the given enumeration variant.
88    ///
89    /// # Arguments
90    ///
91    /// * `variant` - A reference to an enumeration variant.
92    pub const fn get_mut(&mut self, variant: &K) -> &mut V {
93        use_variant_value!(self, variant, i, {
94            return &mut self.table[i].1;
95        });
96    }
97
98    /// Sets the value associated with the given enumeration variant.
99    ///
100    /// # Arguments
101    ///
102    /// * `variant` - A reference to an enumeration variant.
103    /// * `value` - The new value to associate with the variant.
104    /// # Returns
105    /// The old value associated with the variant.
106    pub const fn set(&mut self, variant: &K, value: V) -> V {
107        use_variant_value!(self, variant, i, {
108            return core::mem::replace(&mut self.table[i].1, value);
109        });
110    }
111
112    /// Returns the number of generic N
113    pub const fn len(&self) -> usize {
114        N
115    }
116
117    /// Returns `false` since the table is never empty.
118    pub const fn is_empty(&self) -> bool {
119        false
120    }
121}
122
123mod dev_macros {
124    macro_rules! use_variant_value {
125        ($self:ident, $variant:ident, $i:ident,{$($tt:tt)+}) => {
126            let discriminant = core::mem::discriminant($variant);
127
128            let mut $i = 0;
129            while $i < $self.table.len() {
130                if to_usize($self.table[$i].0) == to_usize(discriminant) {
131                    $($tt)+
132                }
133                $i += 1;
134            }
135            unreachable!();
136        };
137    }
138
139    pub(super) use use_variant_value;
140}