enum_table/lib.rs
1#![doc = include_str!(concat!("../", core::env!("CARGO_PKG_README")))]
2
3#[cfg(test)]
4pub extern crate self as enum_table;
5
6#[cfg(feature = "derive")]
7pub use enum_table_derive::Enumable;
8
9pub mod builder;
10mod intrinsics;
11
12pub mod __private {
13 pub use crate::intrinsics::sort_variants;
14}
15
16mod impls;
17pub use impls::*;
18
19mod macros;
20
21use intrinsics::{cast_variant, into_variant, to_usize};
22
23/// A trait for enumerations that can be used with `EnumTable`.
24///
25/// This trait requires that the enumeration provides a static array of its variants
26/// and a constant representing the count of these variants.
27pub trait Enumable: Copy + 'static {
28 const VARIANTS: &'static [Self];
29 const COUNT: usize = Self::VARIANTS.len();
30}
31
32/// A table that associates each variant of an enumeration with a value.
33///
34/// `EnumTable` is a generic struct that uses an enumeration as keys and stores
35/// associated values. It provides constant-time access to the values based on
36/// the enumeration variant. This is particularly useful when you want to map
37/// enum variants to specific values without the overhead of a `HashMap`.
38///
39/// # Type Parameters
40///
41/// * `K`: The enumeration type that implements the `Enumable` trait. This trait
42/// ensures that the enum provides a static array of its variants and a count
43/// of these variants.
44/// * `V`: The type of values to be associated with each enum variant.
45/// * `N`: The number of variants in the enum, which should match the length of
46/// the static array of variants provided by the `Enumable` trait.
47///
48/// # Note
49/// The `new` method allows for the creation of an `EnumTable` in `const` contexts,
50/// but it does not perform compile-time checks. For enhanced compile-time safety
51/// and convenience, it is advisable to use the [`crate::et`] macro or
52/// [`crate::builder::EnumTableBuilder`], which provide these checks.
53///
54/// # Examples
55///
56/// ```rust
57/// use enum_table::{EnumTable, Enumable};
58///
59/// #[derive(Enumable, Copy, Clone)]
60/// enum Color {
61/// Red,
62/// Green,
63/// Blue,
64/// }
65///
66/// // Create an EnumTable using the new_with_fn method
67/// let table = EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
68/// Color::Red => "Red",
69/// Color::Green => "Green",
70/// Color::Blue => "Blue",
71/// });
72///
73/// // Access values associated with enum variants
74/// assert_eq!(table.get(&Color::Red), &"Red");
75/// assert_eq!(table.get(&Color::Green), &"Green");
76/// assert_eq!(table.get(&Color::Blue), &"Blue");
77/// ```
78pub struct EnumTable<K: Enumable, V, const N: usize> {
79 table: [(usize, V); N],
80 _phantom: core::marker::PhantomData<K>,
81}
82
83impl<K: Enumable, V, const N: usize> EnumTable<K, V, N> {
84 /// Creates a new `EnumTable` with the given table of discriminants and values.
85 /// Typically, you would use the [`crate::et`] macro or [`crate::builder::EnumTableBuilder`] to create an `EnumTable`.
86 pub(crate) const fn new(table: [(usize, V); N]) -> Self {
87 #[cfg(debug_assertions)]
88 const {
89 // Ensure that the variants are sorted by their discriminants.
90 // This is a compile-time check to ensure that the variants are in the correct order.
91 if !intrinsics::is_sorted(K::VARIANTS) {
92 panic!(
93 "Enumable: variants are not sorted by discriminant. Use `enum_table::Enumable` derive macro to ensure correct ordering."
94 );
95 }
96 }
97
98 Self {
99 table,
100 _phantom: core::marker::PhantomData,
101 }
102 }
103
104 /// Create a new EnumTable with a function that takes a variant and returns a value.
105 /// If you want to define it in const, use [`crate::et`] macro
106 /// Creates a new `EnumTable` using a function to generate values for each variant.
107 ///
108 /// # Arguments
109 ///
110 /// * `f` - A function that takes a reference to an enumeration variant and returns
111 /// a value to be associated with that variant.
112 pub fn new_with_fn(mut f: impl FnMut(&K) -> V) -> Self {
113 et!(K, V, { N }, |variant| f(variant))
114 }
115
116 /// Creates a new `EnumTable` using a function that returns a `Result` for each variant.
117 ///
118 /// This method applies the provided closure to each variant of the enum. If the closure
119 /// returns `Ok(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
120 /// If the closure returns `Err(e)` for any variant, the construction is aborted and
121 /// `Err((variant, e))` is returned, where `variant` is the enum variant that caused the error.
122 ///
123 /// # Arguments
124 ///
125 /// * `f` - A closure that takes a reference to an enum variant and returns a `Result<V, E>`.
126 ///
127 /// # Returns
128 ///
129 /// * `Ok(Self)` if all variants succeed.
130 /// * `Err((variant, e))` if any variant fails, containing the failing variant and the error.
131 pub fn try_new_with_fn<E>(mut f: impl FnMut(&K) -> Result<V, E>) -> Result<Self, (K, E)> {
132 Ok(et!(K, V, { N }, |variant| {
133 f(variant).map_err(|e| (*variant, e))?
134 }))
135 }
136
137 /// Creates a new `EnumTable` using a function that returns an `Option` for each variant.
138 ///
139 /// This method applies the provided closure to each variant of the enum. If the closure
140 /// returns `Some(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
141 /// If the closure returns `None` for any variant, the construction is aborted and
142 /// `Err(variant)` is returned, where `variant` is the enum variant that caused the failure.
143 ///
144 /// # Arguments
145 ///
146 /// * `f` - A closure that takes a reference to an enum variant and returns an `Option<V>`.
147 ///
148 /// # Returns
149 ///
150 /// * `Ok(Self)` if all variants succeed.
151 /// * `Err(variant)` if any variant fails, containing the failing variant.
152 pub fn checked_new_with_fn(mut f: impl FnMut(&K) -> Option<V>) -> Result<Self, K> {
153 Ok(et!(K, V, { N }, |variant| f(variant).ok_or(*variant)?))
154 }
155
156 pub(crate) const fn binary_search(&self, variant: &K) -> usize {
157 let discriminant = to_usize(variant);
158 let mut low = 0;
159 let mut high = N;
160
161 while low < high {
162 let mid = low + (high - low) / 2;
163 if self.table[mid].0 < discriminant {
164 low = mid + 1;
165 } else {
166 high = mid;
167 }
168 }
169
170 low
171 }
172
173 /// Returns a reference to the value associated with the given enumeration variant.
174 ///
175 /// # Arguments
176 ///
177 /// * `variant` - A reference to an enumeration variant.
178 pub const fn get(&self, variant: &K) -> &V {
179 let idx = self.binary_search(variant);
180 &self.table[idx].1
181 }
182
183 /// Returns a mutable reference to the value associated with the given enumeration variant.
184 ///
185 /// # Arguments
186 ///
187 /// * `variant` - A reference to an enumeration variant.
188 pub const fn get_mut(&mut self, variant: &K) -> &mut V {
189 let idx = self.binary_search(variant);
190 &mut self.table[idx].1
191 }
192
193 /// Sets the value associated with the given enumeration variant.
194 ///
195 /// # Arguments
196 ///
197 /// * `variant` - A reference to an enumeration variant.
198 /// * `value` - The new value to associate with the variant.
199 /// # Returns
200 /// The old value associated with the variant.
201 pub const fn set(&mut self, variant: &K, value: V) -> V {
202 let idx = self.binary_search(variant);
203 core::mem::replace(&mut self.table[idx].1, value)
204 }
205
206 /// Returns the number of generic N
207 pub const fn len(&self) -> usize {
208 N
209 }
210
211 /// Returns `false` since the table is never empty.
212 pub const fn is_empty(&self) -> bool {
213 false
214 }
215
216 /// Returns an iterator over references to the keys in the table.
217 pub fn keys(&self) -> impl Iterator<Item = &K> {
218 self.table
219 .iter()
220 .map(|(discriminant, _)| cast_variant(discriminant))
221 }
222
223 /// Returns an iterator over references to the values in the table.
224 pub fn values(&self) -> impl Iterator<Item = &V> {
225 self.table.iter().map(|(_, value)| value)
226 }
227
228 /// Returns an iterator over mutable references to the values in the table.
229 pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
230 self.table.iter_mut().map(|(_, value)| value)
231 }
232
233 /// Returns an iterator over mutable references to the values in the table.
234 pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
235 self.table
236 .iter()
237 .map(|(discriminant, value)| (cast_variant(discriminant), value))
238 }
239
240 /// Returns an iterator over mutable references to the values in the table.
241 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
242 self.table
243 .iter_mut()
244 .map(|(discriminant, value)| (cast_variant(discriminant), value))
245 }
246
247 /// Transforms all values in the table using the provided function.
248 ///
249 /// This method consumes the table and creates a new one with values
250 /// transformed by the given closure.
251 ///
252 /// # Arguments
253 ///
254 /// * `f` - A closure that takes an owned value and returns a new value.
255 ///
256 /// # Examples
257 ///
258 /// ```rust
259 /// use enum_table::{EnumTable, Enumable};
260 ///
261 /// #[derive(Enumable, Copy, Clone)]
262 /// enum Size {
263 /// Small,
264 /// Medium,
265 /// Large,
266 /// }
267 ///
268 /// let table = EnumTable::<Size, i32, { Size::COUNT }>::new_with_fn(|size| match size {
269 /// Size::Small => 1,
270 /// Size::Medium => 2,
271 /// Size::Large => 3,
272 /// });
273 ///
274 /// let doubled = table.map(|value| value * 2);
275 ///
276 /// assert_eq!(doubled.get(&Size::Small), &2);
277 /// assert_eq!(doubled.get(&Size::Medium), &4);
278 /// assert_eq!(doubled.get(&Size::Large), &6);
279 /// ```
280 pub fn map<U>(self, mut f: impl FnMut(V) -> U) -> EnumTable<K, U, N> {
281 EnumTable::new(
282 self.table
283 .map(|(discriminant, value)| (discriminant, f(value))),
284 )
285 }
286
287 /// Transforms all values in the table in-place using the provided function.
288 ///
289 /// # Arguments
290 ///
291 /// * `f` - A closure that takes a mutable reference to a value and modifies it.
292 ///
293 /// # Examples
294 ///
295 /// ```rust
296 /// use enum_table::{EnumTable, Enumable};
297 ///
298 /// #[derive(Enumable, Copy, Clone)]
299 /// enum Level {
300 /// Low,
301 /// Medium,
302 /// High,
303 /// }
304 ///
305 /// let mut table = EnumTable::<Level, i32, { Level::COUNT }>::new_with_fn(|level| match level {
306 /// Level::Low => 10,
307 /// Level::Medium => 20,
308 /// Level::High => 30,
309 /// });
310 ///
311 /// table.map_mut(|value| *value += 5);
312 ///
313 /// assert_eq!(table.get(&Level::Low), &15);
314 /// assert_eq!(table.get(&Level::Medium), &25);
315 /// assert_eq!(table.get(&Level::High), &35);
316 /// ```
317 pub fn map_mut(&mut self, mut f: impl FnMut(&mut V)) {
318 self.table.iter_mut().for_each(|(_, value)| {
319 f(value);
320 });
321 }
322}
323
324impl<K: Enumable, V, const N: usize> EnumTable<K, Option<V>, N> {
325 /// Creates a new `EnumTable` with `None` values for each variant.
326 pub const fn new_fill_with_none() -> Self {
327 et!(K, Option<V>, { N }, |variant| None)
328 }
329
330 /// Clears the table, setting each value to `None`.
331 pub fn clear_to_none(&mut self) {
332 for (_, value) in &mut self.table {
333 *value = None;
334 }
335 }
336}
337
338impl<K: Enumable, V: Copy, const N: usize> EnumTable<K, V, N> {
339 /// Creates a new `EnumTable` with the same copied value for each variant.
340 ///
341 /// This method initializes the table with the same value for each
342 /// variant of the enumeration. The value must implement `Copy`.
343 ///
344 /// # Arguments
345 ///
346 /// * `value` - The value to copy for each enum variant.
347 ///
348 /// # Examples
349 ///
350 /// ```rust
351 /// use enum_table::{EnumTable, Enumable};
352 ///
353 /// #[derive(Enumable, Copy, Clone)]
354 /// enum Status {
355 /// Active,
356 /// Inactive,
357 /// Pending,
358 /// }
359 ///
360 /// let table = EnumTable::<Status, i32, { Status::COUNT }>::new_fill_with_copy(42);
361 ///
362 /// assert_eq!(table.get(&Status::Active), &42);
363 /// assert_eq!(table.get(&Status::Inactive), &42);
364 /// assert_eq!(table.get(&Status::Pending), &42);
365 /// ```
366 pub const fn new_fill_with_copy(value: V) -> Self {
367 et!(K, V, { N }, |variant| value)
368 }
369}
370
371impl<K: Enumable, V: Default, const N: usize> EnumTable<K, V, N> {
372 /// Creates a new `EnumTable` with default values for each variant.
373 ///
374 /// This method initializes the table with the default value of type `V` for each
375 /// variant of the enumeration.
376 pub fn new_fill_with_default() -> Self {
377 et!(K, V, { N }, |variant| V::default())
378 }
379
380 /// Clears the table, setting each value to its default.
381 pub fn clear_to_default(&mut self) {
382 for (_, value) in &mut self.table {
383 *value = V::default();
384 }
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
393 enum Color {
394 Red = 33,
395 Green = 11,
396 Blue = 222,
397 }
398
399 const TABLES: EnumTable<Color, &'static str, { Color::COUNT }> =
400 crate::et!(Color, &'static str, |color| match color {
401 Color::Red => "Red",
402 Color::Green => "Green",
403 Color::Blue => "Blue",
404 });
405
406 #[test]
407 fn new_with_fn() {
408 let table =
409 EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
410 Color::Red => "Red",
411 Color::Green => "Green",
412 Color::Blue => "Blue",
413 });
414
415 assert_eq!(table.get(&Color::Red), &"Red");
416 assert_eq!(table.get(&Color::Green), &"Green");
417 assert_eq!(table.get(&Color::Blue), &"Blue");
418 }
419
420 #[test]
421 fn try_new_with_fn() {
422 let table =
423 EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
424 |color| match color {
425 Color::Red => Ok::<&'static str, core::convert::Infallible>("Red"),
426 Color::Green => Ok("Green"),
427 Color::Blue => Ok("Blue"),
428 },
429 );
430
431 assert!(table.is_ok());
432 let table = table.unwrap();
433
434 assert_eq!(table.get(&Color::Red), &"Red");
435 assert_eq!(table.get(&Color::Green), &"Green");
436 assert_eq!(table.get(&Color::Blue), &"Blue");
437
438 let error_table = EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
439 |color| match color {
440 Color::Red => Ok("Red"),
441 Color::Green => Err("Error on Green"),
442 Color::Blue => Ok("Blue"),
443 },
444 );
445
446 assert!(error_table.is_err());
447 let (variant, error) = error_table.unwrap_err();
448
449 assert_eq!(variant, Color::Green);
450 assert_eq!(error, "Error on Green");
451 }
452
453 #[test]
454 fn checked_new_with_fn() {
455 let table =
456 EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
457 match color {
458 Color::Red => Some("Red"),
459 Color::Green => Some("Green"),
460 Color::Blue => Some("Blue"),
461 }
462 });
463
464 assert!(table.is_ok());
465 let table = table.unwrap();
466
467 assert_eq!(table.get(&Color::Red), &"Red");
468 assert_eq!(table.get(&Color::Green), &"Green");
469 assert_eq!(table.get(&Color::Blue), &"Blue");
470
471 let error_table =
472 EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
473 match color {
474 Color::Red => Some("Red"),
475 Color::Green => None,
476 Color::Blue => Some("Blue"),
477 }
478 });
479
480 assert!(error_table.is_err());
481 let variant = error_table.unwrap_err();
482
483 assert_eq!(variant, Color::Green);
484 }
485
486 #[test]
487 fn get() {
488 assert_eq!(TABLES.get(&Color::Red), &"Red");
489 assert_eq!(TABLES.get(&Color::Green), &"Green");
490 assert_eq!(TABLES.get(&Color::Blue), &"Blue");
491 }
492
493 #[test]
494 fn get_mut() {
495 let mut table = TABLES;
496 assert_eq!(table.get_mut(&Color::Red), &mut "Red");
497 assert_eq!(table.get_mut(&Color::Green), &mut "Green");
498 assert_eq!(table.get_mut(&Color::Blue), &mut "Blue");
499
500 *table.get_mut(&Color::Red) = "Changed Red";
501 *table.get_mut(&Color::Green) = "Changed Green";
502 *table.get_mut(&Color::Blue) = "Changed Blue";
503
504 assert_eq!(table.get(&Color::Red), &"Changed Red");
505 assert_eq!(table.get(&Color::Green), &"Changed Green");
506 assert_eq!(table.get(&Color::Blue), &"Changed Blue");
507 }
508
509 #[test]
510 fn set() {
511 let mut table = TABLES;
512 assert_eq!(table.set(&Color::Red, "New Red"), "Red");
513 assert_eq!(table.set(&Color::Green, "New Green"), "Green");
514 assert_eq!(table.set(&Color::Blue, "New Blue"), "Blue");
515
516 assert_eq!(table.get(&Color::Red), &"New Red");
517 assert_eq!(table.get(&Color::Green), &"New Green");
518 assert_eq!(table.get(&Color::Blue), &"New Blue");
519 }
520
521 #[test]
522 fn keys() {
523 let keys: Vec<_> = TABLES.keys().collect();
524 assert_eq!(keys, vec![&Color::Green, &Color::Red, &Color::Blue]);
525 }
526
527 #[test]
528 fn values() {
529 let values: Vec<_> = TABLES.values().collect();
530 assert_eq!(values, vec![&"Green", &"Red", &"Blue"]);
531 }
532
533 #[test]
534 fn iter() {
535 let iter: Vec<_> = TABLES.iter().collect();
536 assert_eq!(
537 iter,
538 vec![
539 (&Color::Green, &"Green"),
540 (&Color::Red, &"Red"),
541 (&Color::Blue, &"Blue")
542 ]
543 );
544 }
545
546 #[test]
547 fn iter_mut() {
548 let mut table = TABLES;
549 for (key, value) in table.iter_mut() {
550 *value = match key {
551 Color::Red => "Changed Red",
552 Color::Green => "Changed Green",
553 Color::Blue => "Changed Blue",
554 };
555 }
556 let iter: Vec<_> = table.iter().collect();
557 assert_eq!(
558 iter,
559 vec![
560 (&Color::Green, &"Changed Green"),
561 (&Color::Red, &"Changed Red"),
562 (&Color::Blue, &"Changed Blue")
563 ]
564 );
565 }
566
567 macro_rules! run_variants_test {
568 ($($variant:ident),+) => {{
569 #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
570 #[repr(u8)]
571 enum Test {
572 $($variant,)*
573 }
574
575 let map = EnumTable::<Test, &'static str, { Test::COUNT }>::new_with_fn(|t| match t {
576 $(Test::$variant => stringify!($variant),)*
577 });
578 $(
579 assert_eq!(map.get(&Test::$variant), &stringify!($variant));
580 )*
581 }};
582 }
583
584 #[test]
585 fn binary_search_correct_variants() {
586 run_variants_test!(A);
587 run_variants_test!(A, B);
588 run_variants_test!(A, B, C);
589 run_variants_test!(A, B, C, D);
590 run_variants_test!(A, B, C, D, E);
591 }
592}