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