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}