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}