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>(t: T) -> usize {
22    use core::mem::ManuallyDrop;
23
24    #[inline(always)]
25    const fn cast<T, U>(t: T) -> U {
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        #[cfg(target_pointer_width = "64")]
34        8 => cast::<T, 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
175mod dev_macros {
176    macro_rules! use_variant_value {
177        ($self:ident, $variant:ident, $i:ident,{$($tt:tt)+}) => {
178            let discriminant = to_usize(core::mem::discriminant($variant));
179
180            let mut $i = 0;
181            while $i < $self.table.len() {
182                if $self.table[$i].0 == discriminant {
183                    $($tt)+
184                }
185                $i += 1;
186            }
187            unreachable!();
188        };
189    }
190
191    pub(super) use use_variant_value;
192}