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        unsafe { std::mem::transmute(t) }
25    }
26
27    let t = &t;
28
29    match const { core::mem::size_of::<T>() } {
30        1 => *cast::<u8>(t) as usize,
31        2 => *cast::<u16>(t) as usize,
32        4 => *cast::<u32>(t) as usize,
33        #[cfg(target_pointer_width = "64")]
34        8 => *cast::<u64>(t) as usize,
35        #[cfg(target_pointer_width = "32")]
36        8 => panic!("Unsupported size: 64-bit value found on a 32-bit architecture"),
37        _ => panic!("Values larger than u64 are not supported"),
38    }
39}
40
41/// A table that associates each variant of an enumeration with a value.
42///
43/// `EnumTable` is a generic struct that uses an enumeration as keys and stores
44/// associated values. It provides constant-time access to the values based on
45/// the enumeration variant. This is particularly useful when you want to map
46/// enum variants to specific values without the overhead of a `HashMap`.
47///
48/// # Type Parameters
49///
50/// * `K`: The enumeration type that implements the `Enumable` trait. This trait
51///   ensures that the enum provides a static array of its variants and a count
52///   of these variants.
53/// * `V`: The type of values to be associated with each enum variant.
54/// * `N`: The number of variants in the enum, which should match the length of
55///   the static array of variants provided by the `Enumable` trait.
56///
57/// # Note
58/// The `new` method allows for the creation of an `EnumTable` in `const` contexts,
59/// but it does not perform compile-time checks. For enhanced compile-time safety
60/// and convenience, it is advisable to use the [`crate::et`] macro or
61/// [`crate::builder::EnumTableBuilder`], which provide these checks.
62///
63/// # Examples
64///
65/// ```rust
66/// use enum_table::{EnumTable, Enumable};
67///
68/// #[derive(Enumable)]
69/// enum Color {
70///     Red,
71///     Green,
72///     Blue,
73/// }
74///
75/// // Create an EnumTable using the new_with_fn method
76/// let table = EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
77///     Color::Red => "Red",
78///     Color::Green => "Green",
79///     Color::Blue => "Blue",
80/// });
81///
82/// // Access values associated with enum variants
83/// assert_eq!(table.get(&Color::Red), &"Red");
84/// assert_eq!(table.get(&Color::Green), &"Green");
85/// assert_eq!(table.get(&Color::Blue), &"Blue");
86/// ```
87pub struct EnumTable<K: Enumable, V, const N: usize> {
88    table: [(usize, V); N],
89    _phantom: core::marker::PhantomData<K>,
90}
91
92impl<K: Enumable, V, const N: usize> EnumTable<K, V, N> {
93    /// Creates a new `EnumTable` with the given table of discriminants and values.
94    /// Typically, you would use the [`crate::et`] macro or the [`crate::builder::EnumTableBuilder`] instead.
95    ///
96    ///
97    /// # Arguments
98    ///
99    /// * `table` - An array of tuples where each tuple contains a discriminant of
100    ///   an enumeration variant and its associated value.
101    pub const fn new(table: [(usize, V); N]) -> Self {
102        Self {
103            table,
104            _phantom: core::marker::PhantomData,
105        }
106    }
107
108    /// Create a new EnumTable with a function that takes a variant and returns a value.
109    /// If you want to define it in const, use [`crate::et`] macro
110    /// Creates a new `EnumTable` using a function to generate values for each variant.
111    ///
112    /// # Arguments
113    ///
114    /// * `f` - A function that takes a reference to an enumeration variant and returns
115    ///   a value to be associated with that variant.
116    pub fn new_with_fn(mut f: impl FnMut(&K) -> V) -> Self {
117        let table = core::array::from_fn(|i| {
118            let k = &K::VARIANTS[i];
119            (to_usize(core::mem::discriminant(k)), f(k))
120        });
121
122        Self {
123            table,
124            _phantom: core::marker::PhantomData,
125        }
126    }
127
128    /// Returns a reference to the value associated with the given enumeration variant.
129    ///
130    /// # Arguments
131    ///
132    /// * `variant` - A reference to an enumeration variant.
133    pub const fn get(&self, variant: &K) -> &V {
134        use_variant_value!(self, variant, i, {
135            return &self.table[i].1;
136        });
137    }
138
139    /// Returns a mutable reference to the value associated with the given enumeration variant.
140    ///
141    /// # Arguments
142    ///
143    /// * `variant` - A reference to an enumeration variant.
144    pub const fn get_mut(&mut self, variant: &K) -> &mut V {
145        use_variant_value!(self, variant, i, {
146            return &mut self.table[i].1;
147        });
148    }
149
150    /// Sets the value associated with the given enumeration variant.
151    ///
152    /// # Arguments
153    ///
154    /// * `variant` - A reference to an enumeration variant.
155    /// * `value` - The new value to associate with the variant.
156    /// # Returns
157    /// The old value associated with the variant.
158    pub const fn set(&mut self, variant: &K, value: V) -> V {
159        use_variant_value!(self, variant, i, {
160            return core::mem::replace(&mut self.table[i].1, value);
161        });
162    }
163
164    /// Returns the number of generic N
165    pub const fn len(&self) -> usize {
166        N
167    }
168
169    /// Returns `false` since the table is never empty.
170    pub const fn is_empty(&self) -> bool {
171        false
172    }
173
174    /// Returns the reference to the value associated with the given discriminant.
175    ///
176    /// # Arguments
177    ///
178    /// * `discriminant` - The discriminant of the enumeration variant.
179    ///
180    /// # Returns
181    /// An `Option` containing a reference to the value associated with the discriminant,
182    /// or `None` if the discriminant is not found in the table.
183    pub const fn get_by_discriminant(&self, discriminant: usize) -> Option<&V> {
184        let mut i = 0;
185        while i < self.table.len() {
186            if self.table[i].0 == discriminant {
187                return Some(&self.table[i].1);
188            }
189            i += 1;
190        }
191        None
192    }
193
194    /// Returns a mutable reference to the value associated with the given discriminant.
195    ///
196    /// # Arguments
197    ///
198    /// * `discriminant` - The discriminant of the enumeration variant.
199    ///
200    /// # Returns
201    ///
202    /// An `Option` containing a mutable reference to the value associated with the discriminant,
203    /// or `None` if the discriminant is not found in the table.
204    pub const fn get_mut_by_discriminant(&mut self, discriminant: usize) -> Option<&mut V> {
205        let mut i = 0;
206        while i < self.table.len() {
207            if self.table[i].0 == discriminant {
208                return Some(&mut self.table[i].1);
209            }
210            i += 1;
211        }
212        None
213    }
214
215    /// Sets the value associated with the given discriminant.
216    ///
217    /// # Arguments
218    ///
219    /// * `discriminant` - The discriminant of the enumeration variant.
220    /// * `value` - The new value to associate with the discriminant.
221    ///
222    /// # Returns
223    ///
224    /// Returns `Ok` with the old value associated with the discriminant if it exists,
225    /// or `Err` with the provided value if the discriminant is not found in the table.
226    /// (Returns the provided value in `Err` because this function is `const` and dropping
227    /// values is not allowed in a const context.)
228    pub const fn set_by_discriminant(&mut self, discriminant: usize, value: V) -> Result<V, V> {
229        let mut i = 0;
230        while i < self.table.len() {
231            if self.table[i].0 == discriminant {
232                return Ok(core::mem::replace(&mut self.table[i].1, value));
233            }
234            i += 1;
235        }
236        Err(value)
237    }
238
239    pub fn keys(&self) -> impl Iterator<Item = &K> {
240        self.table
241            .iter()
242            .map(|(discriminant, _)| unsafe { core::mem::transmute::<usize, &K>(*discriminant) })
243    }
244
245    pub fn values(&self) -> impl Iterator<Item = &V> {
246        self.table.iter().map(|(_, value)| value)
247    }
248
249    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
250        self.table.iter().map(|(discriminant, value)| {
251            (
252                unsafe { core::mem::transmute::<usize, &K>(*discriminant) },
253                value,
254            )
255        })
256    }
257
258    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
259        self.table.iter_mut().map(|(discriminant, value)| {
260            (
261                unsafe { core::mem::transmute::<usize, &K>(*discriminant) },
262                value,
263            )
264        })
265    }
266
267    pub fn iter_by_discriminant(&self) -> impl Iterator<Item = (usize, &V)> {
268        self.table
269            .iter()
270            .map(|(discriminant, value)| (*discriminant, value))
271    }
272}
273
274mod dev_macros {
275    macro_rules! use_variant_value {
276        ($self:ident, $variant:ident, $i:ident,{$($tt:tt)+}) => {
277            let discriminant = to_usize(core::mem::discriminant($variant));
278
279            let mut $i = 0;
280            while $i < $self.table.len() {
281                if $self.table[$i].0 == discriminant {
282                    $($tt)+
283                }
284                $i += 1;
285            }
286            unreachable!();
287        };
288    }
289
290    pub(super) use use_variant_value;
291}