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 efficient logarithmic-time access (O(log N))
36/// to the values based on the enumeration variant. This is particularly useful
37/// when you want to map enum variants to specific values without the overhead
38/// of a `HashMap`.
39///
40/// # Guarantees and Design
41///
42/// The core design principle of `EnumTable` is that an instance is guaranteed to hold a
43/// value for every variant of the enum `K`. This guarantee allows for a cleaner API
44/// than general-purpose map structures.
45///
46/// For example, the [`Self::get`] method returns `&V` directly. This is in contrast to
47/// [`std::collections::HashMap::get`], which returns an `Option<&V>` because a key may or may not be
48/// present. With `EnumTable`, the presence of all keys (variants) is a type-level
49/// invariant, eliminating the need for `unwrap()` or other `Option` handling.
50///
51/// If you need to handle cases where a value might not be present or will be set
52/// later, you can use `Option<V>` as the value type: `EnumTable<K, Option<V>, N>`.
53/// The struct provides convenient methods like [`Self::new_fill_with_none`] for this pattern.
54///
55/// # Type Parameters
56///
57/// * `K`: The enumeration type that implements the `Enumable` trait. This trait
58/// ensures that the enum provides a static array of its variants and a count
59/// of these variants.
60/// * `V`: The type of values to be associated with each enum variant.
61/// * `N`: The number of variants in the enum, which should match the length of
62/// the static array of variants provided by the `Enumable` trait.
63///
64/// # Examples
65///
66/// ```rust
67/// use enum_table::{EnumTable, Enumable};
68///
69/// #[derive(Enumable, Copy, Clone)]
70/// enum Color {
71/// Red,
72/// Green,
73/// Blue,
74/// }
75///
76/// // Create an EnumTable using the new_with_fn method
77/// let table = EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
78/// Color::Red => "Red",
79/// Color::Green => "Green",
80/// Color::Blue => "Blue",
81/// });
82///
83/// // Access values associated with enum variants
84/// assert_eq!(table.get(&Color::Red), &"Red");
85/// assert_eq!(table.get(&Color::Green), &"Green");
86/// assert_eq!(table.get(&Color::Blue), &"Blue");
87/// ```
88pub struct EnumTable<K: Enumable, V, const N: usize> {
89 table: [(usize, V); N],
90 _phantom: core::marker::PhantomData<K>,
91}
92
93impl<K: Enumable, V, const N: usize> EnumTable<K, V, N> {
94 /// Creates a new `EnumTable` with the given table of discriminants and values.
95 /// Typically, you would use the [`crate::et`] macro or [`crate::builder::EnumTableBuilder`] to create an `EnumTable`.
96 pub(crate) const fn new(table: [(usize, V); N]) -> Self {
97 #[cfg(debug_assertions)]
98 const {
99 // Ensure that the variants are sorted by their discriminants.
100 // This is a compile-time check to ensure that the variants are in the correct order.
101 if !intrinsics::is_sorted(K::VARIANTS) {
102 panic!(
103 "Enumable: variants are not sorted by discriminant. Use `enum_table::Enumable` derive macro to ensure correct ordering."
104 );
105 }
106 }
107
108 Self {
109 table,
110 _phantom: core::marker::PhantomData,
111 }
112 }
113
114 /// Create a new EnumTable with a function that takes a variant and returns a value.
115 /// If you want to define it in const, use [`crate::et`] macro
116 /// Creates a new `EnumTable` using a function to generate values for each variant.
117 ///
118 /// # Arguments
119 ///
120 /// * `f` - A function that takes a reference to an enumeration variant and returns
121 /// a value to be associated with that variant.
122 pub fn new_with_fn(mut f: impl FnMut(&K) -> V) -> Self {
123 et!(K, V, { N }, |variant| f(variant))
124 }
125
126 /// Creates a new `EnumTable` using a function that returns a `Result` for each variant.
127 ///
128 /// This method applies the provided closure to each variant of the enum. If the closure
129 /// returns `Ok(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
130 /// If the closure returns `Err(e)` for any variant, the construction is aborted and
131 /// `Err((variant, e))` is returned, where `variant` is the enum variant that caused the error.
132 ///
133 /// # Arguments
134 ///
135 /// * `f` - A closure that takes a reference to an enum variant and returns a `Result<V, E>`.
136 ///
137 /// # Returns
138 ///
139 /// * `Ok(Self)` if all variants succeed.
140 /// * `Err((variant, e))` if any variant fails, containing the failing variant and the error.
141 pub fn try_new_with_fn<E>(mut f: impl FnMut(&K) -> Result<V, E>) -> Result<Self, (K, E)> {
142 Ok(et!(K, V, { N }, |variant| {
143 f(variant).map_err(|e| (*variant, e))?
144 }))
145 }
146
147 /// Creates a new `EnumTable` using a function that returns an `Option` for each variant.
148 ///
149 /// This method applies the provided closure to each variant of the enum. If the closure
150 /// returns `Some(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
151 /// If the closure returns `None` for any variant, the construction is aborted and
152 /// `Err(variant)` is returned, where `variant` is the enum variant that caused the failure.
153 ///
154 /// # Arguments
155 ///
156 /// * `f` - A closure that takes a reference to an enum variant and returns an `Option<V>`.
157 ///
158 /// # Returns
159 ///
160 /// * `Ok(Self)` if all variants succeed.
161 /// * `Err(variant)` if any variant fails, containing the failing variant.
162 pub fn checked_new_with_fn(mut f: impl FnMut(&K) -> Option<V>) -> Result<Self, K> {
163 Ok(et!(K, V, { N }, |variant| f(variant).ok_or(*variant)?))
164 }
165
166 pub(crate) const fn binary_search(&self, variant: &K) -> usize {
167 let discriminant = to_usize(variant);
168 let mut low = 0;
169 let mut high = N;
170
171 while low < high {
172 let mid = low + (high - low) / 2;
173 if self.table[mid].0 < discriminant {
174 low = mid + 1;
175 } else {
176 high = mid;
177 }
178 }
179
180 low
181 }
182
183 /// Returns a 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(&self, variant: &K) -> &V {
189 let idx = self.binary_search(variant);
190 &self.table[idx].1
191 }
192
193 /// Returns a mutable reference to the value associated with the given enumeration variant.
194 ///
195 /// # Arguments
196 ///
197 /// * `variant` - A reference to an enumeration variant.
198 pub const fn get_mut(&mut self, variant: &K) -> &mut V {
199 let idx = self.binary_search(variant);
200 &mut self.table[idx].1
201 }
202
203 /// Sets the value associated with the given enumeration variant.
204 ///
205 /// # Arguments
206 ///
207 /// * `variant` - A reference to an enumeration variant.
208 /// * `value` - The new value to associate with the variant.
209 /// # Returns
210 /// The old value associated with the variant.
211 pub const fn set(&mut self, variant: &K, value: V) -> V {
212 let idx = self.binary_search(variant);
213 core::mem::replace(&mut self.table[idx].1, value)
214 }
215
216 /// Returns the number of generic N
217 pub const fn len(&self) -> usize {
218 N
219 }
220
221 /// Returns `false` since the table is never empty.
222 pub const fn is_empty(&self) -> bool {
223 false
224 }
225
226 /// Returns an iterator over references to the keys in the table.
227 pub fn keys(&self) -> impl Iterator<Item = &K> {
228 self.table
229 .iter()
230 .map(|(discriminant, _)| cast_variant(discriminant))
231 }
232
233 /// Returns an iterator over references to the values in the table.
234 pub fn values(&self) -> impl Iterator<Item = &V> {
235 self.table.iter().map(|(_, value)| value)
236 }
237
238 /// Returns an iterator over mutable references to the values in the table.
239 pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
240 self.table.iter_mut().map(|(_, value)| value)
241 }
242
243 /// Returns an iterator over mutable references to the values in the table.
244 pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
245 self.table
246 .iter()
247 .map(|(discriminant, value)| (cast_variant(discriminant), value))
248 }
249
250 /// Returns an iterator over mutable references to the values in the table.
251 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
252 self.table
253 .iter_mut()
254 .map(|(discriminant, value)| (cast_variant(discriminant), value))
255 }
256
257 /// Transforms all values in the table using the provided function.
258 ///
259 /// This method consumes the table and creates a new one with values
260 /// transformed by the given closure.
261 ///
262 /// # Arguments
263 ///
264 /// * `f` - A closure that takes an owned value and returns a new value.
265 ///
266 /// # Examples
267 ///
268 /// ```rust
269 /// use enum_table::{EnumTable, Enumable};
270 ///
271 /// #[derive(Enumable, Copy, Clone)]
272 /// enum Size {
273 /// Small,
274 /// Medium,
275 /// Large,
276 /// }
277 ///
278 /// let table = EnumTable::<Size, i32, { Size::COUNT }>::new_with_fn(|size| match size {
279 /// Size::Small => 1,
280 /// Size::Medium => 2,
281 /// Size::Large => 3,
282 /// });
283 ///
284 /// let doubled = table.map(|value| value * 2);
285 ///
286 /// assert_eq!(doubled.get(&Size::Small), &2);
287 /// assert_eq!(doubled.get(&Size::Medium), &4);
288 /// assert_eq!(doubled.get(&Size::Large), &6);
289 /// ```
290 pub fn map<U>(self, mut f: impl FnMut(V) -> U) -> EnumTable<K, U, N> {
291 EnumTable::new(
292 self.table
293 .map(|(discriminant, value)| (discriminant, f(value))),
294 )
295 }
296
297 /// Transforms all values in the table using the provided function, with access to the key.
298 ///
299 /// This method consumes the table and creates a new one with values
300 /// transformed by the given closure, which receives both the key and the value.
301 ///
302 /// # Arguments
303 ///
304 /// * `f` - A closure that takes a key reference and an owned value, and returns a new value.
305 pub fn map_with_key<U>(self, mut f: impl FnMut(&K, V) -> U) -> EnumTable<K, U, N> {
306 EnumTable::new(
307 self.table
308 .map(|(discriminant, value)| (discriminant, f(cast_variant(&discriminant), value))),
309 )
310 }
311
312 /// Transforms all values in the table in-place using the provided function.
313 ///
314 /// # Arguments
315 ///
316 /// * `f` - A closure that takes a mutable reference to a value and modifies it.
317 ///
318 /// # Examples
319 ///
320 /// ```rust
321 /// use enum_table::{EnumTable, Enumable};
322 ///
323 /// #[derive(Enumable, Copy, Clone)]
324 /// enum Level {
325 /// Low,
326 /// Medium,
327 /// High,
328 /// }
329 ///
330 /// let mut table = EnumTable::<Level, i32, { Level::COUNT }>::new_with_fn(|level| match level {
331 /// Level::Low => 10,
332 /// Level::Medium => 20,
333 /// Level::High => 30,
334 /// });
335 ///
336 /// table.map_mut(|value| *value += 5);
337 ///
338 /// assert_eq!(table.get(&Level::Low), &15);
339 /// assert_eq!(table.get(&Level::Medium), &25);
340 /// assert_eq!(table.get(&Level::High), &35);
341 /// ```
342 pub fn map_mut(&mut self, mut f: impl FnMut(&mut V)) {
343 self.table.iter_mut().for_each(|(_, value)| {
344 f(value);
345 });
346 }
347
348 /// Transforms all values in the table in-place using the provided function, with access to the key.
349 ///
350 /// # Arguments
351 ///
352 /// * `f` - A closure that takes a key reference and a mutable reference to a value, and modifies it.
353 pub fn map_mut_with_key(&mut self, mut f: impl FnMut(&K, &mut V)) {
354 self.table.iter_mut().for_each(|(discriminant, value)| {
355 f(cast_variant(discriminant), value);
356 });
357 }
358}
359
360impl<K: Enumable, V, const N: usize> EnumTable<K, Option<V>, N> {
361 /// Creates a new `EnumTable` with `None` values for each variant.
362 pub const fn new_fill_with_none() -> Self {
363 et!(K, Option<V>, { N }, |variant| None)
364 }
365
366 /// Clears the table, setting each value to `None`.
367 pub fn clear_to_none(&mut self) {
368 for (_, value) in &mut self.table {
369 *value = None;
370 }
371 }
372}
373
374impl<K: Enumable, V: Copy, const N: usize> EnumTable<K, V, N> {
375 /// Creates a new `EnumTable` with the same copied value for each variant.
376 ///
377 /// This method initializes the table with the same value for each
378 /// variant of the enumeration. The value must implement `Copy`.
379 ///
380 /// # Arguments
381 ///
382 /// * `value` - The value to copy for each enum variant.
383 ///
384 /// # Examples
385 ///
386 /// ```rust
387 /// use enum_table::{EnumTable, Enumable};
388 ///
389 /// #[derive(Enumable, Copy, Clone)]
390 /// enum Status {
391 /// Active,
392 /// Inactive,
393 /// Pending,
394 /// }
395 ///
396 /// let table = EnumTable::<Status, i32, { Status::COUNT }>::new_fill_with_copy(42);
397 ///
398 /// assert_eq!(table.get(&Status::Active), &42);
399 /// assert_eq!(table.get(&Status::Inactive), &42);
400 /// assert_eq!(table.get(&Status::Pending), &42);
401 /// ```
402 pub const fn new_fill_with_copy(value: V) -> Self {
403 et!(K, V, { N }, |variant| value)
404 }
405}
406
407impl<K: Enumable, V: Default, const N: usize> EnumTable<K, V, N> {
408 /// Creates a new `EnumTable` with default values for each variant.
409 ///
410 /// This method initializes the table with the default value of type `V` for each
411 /// variant of the enumeration.
412 pub fn new_fill_with_default() -> Self {
413 et!(K, V, { N }, |variant| V::default())
414 }
415
416 /// Clears the table, setting each value to its default.
417 pub fn clear_to_default(&mut self) {
418 for (_, value) in &mut self.table {
419 *value = V::default();
420 }
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
429 enum Color {
430 Red = 33,
431 Green = 11,
432 Blue = 222,
433 }
434
435 const TABLES: EnumTable<Color, &'static str, { Color::COUNT }> =
436 crate::et!(Color, &'static str, |color| match color {
437 Color::Red => "Red",
438 Color::Green => "Green",
439 Color::Blue => "Blue",
440 });
441
442 #[test]
443 fn new_with_fn() {
444 let table =
445 EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
446 Color::Red => "Red",
447 Color::Green => "Green",
448 Color::Blue => "Blue",
449 });
450
451 assert_eq!(table.get(&Color::Red), &"Red");
452 assert_eq!(table.get(&Color::Green), &"Green");
453 assert_eq!(table.get(&Color::Blue), &"Blue");
454 }
455
456 #[test]
457 fn try_new_with_fn() {
458 let table =
459 EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
460 |color| match color {
461 Color::Red => Ok::<&'static str, core::convert::Infallible>("Red"),
462 Color::Green => Ok("Green"),
463 Color::Blue => Ok("Blue"),
464 },
465 );
466
467 assert!(table.is_ok());
468 let table = table.unwrap();
469
470 assert_eq!(table.get(&Color::Red), &"Red");
471 assert_eq!(table.get(&Color::Green), &"Green");
472 assert_eq!(table.get(&Color::Blue), &"Blue");
473
474 let error_table = EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
475 |color| match color {
476 Color::Red => Ok("Red"),
477 Color::Green => Err("Error on Green"),
478 Color::Blue => Ok("Blue"),
479 },
480 );
481
482 assert!(error_table.is_err());
483 let (variant, error) = error_table.unwrap_err();
484
485 assert_eq!(variant, Color::Green);
486 assert_eq!(error, "Error on Green");
487 }
488
489 #[test]
490 fn checked_new_with_fn() {
491 let table =
492 EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
493 match color {
494 Color::Red => Some("Red"),
495 Color::Green => Some("Green"),
496 Color::Blue => Some("Blue"),
497 }
498 });
499
500 assert!(table.is_ok());
501 let table = table.unwrap();
502
503 assert_eq!(table.get(&Color::Red), &"Red");
504 assert_eq!(table.get(&Color::Green), &"Green");
505 assert_eq!(table.get(&Color::Blue), &"Blue");
506
507 let error_table =
508 EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
509 match color {
510 Color::Red => Some("Red"),
511 Color::Green => None,
512 Color::Blue => Some("Blue"),
513 }
514 });
515
516 assert!(error_table.is_err());
517 let variant = error_table.unwrap_err();
518
519 assert_eq!(variant, Color::Green);
520 }
521
522 #[test]
523 fn get() {
524 assert_eq!(TABLES.get(&Color::Red), &"Red");
525 assert_eq!(TABLES.get(&Color::Green), &"Green");
526 assert_eq!(TABLES.get(&Color::Blue), &"Blue");
527 }
528
529 #[test]
530 fn get_mut() {
531 let mut table = TABLES;
532 assert_eq!(table.get_mut(&Color::Red), &mut "Red");
533 assert_eq!(table.get_mut(&Color::Green), &mut "Green");
534 assert_eq!(table.get_mut(&Color::Blue), &mut "Blue");
535
536 *table.get_mut(&Color::Red) = "Changed Red";
537 *table.get_mut(&Color::Green) = "Changed Green";
538 *table.get_mut(&Color::Blue) = "Changed Blue";
539
540 assert_eq!(table.get(&Color::Red), &"Changed Red");
541 assert_eq!(table.get(&Color::Green), &"Changed Green");
542 assert_eq!(table.get(&Color::Blue), &"Changed Blue");
543 }
544
545 #[test]
546 fn set() {
547 let mut table = TABLES;
548 assert_eq!(table.set(&Color::Red, "New Red"), "Red");
549 assert_eq!(table.set(&Color::Green, "New Green"), "Green");
550 assert_eq!(table.set(&Color::Blue, "New Blue"), "Blue");
551
552 assert_eq!(table.get(&Color::Red), &"New Red");
553 assert_eq!(table.get(&Color::Green), &"New Green");
554 assert_eq!(table.get(&Color::Blue), &"New Blue");
555 }
556
557 #[test]
558 fn keys() {
559 let keys: Vec<_> = TABLES.keys().collect();
560 assert_eq!(keys, vec![&Color::Green, &Color::Red, &Color::Blue]);
561 }
562
563 #[test]
564 fn values() {
565 let values: Vec<_> = TABLES.values().collect();
566 assert_eq!(values, vec![&"Green", &"Red", &"Blue"]);
567 }
568
569 #[test]
570 fn iter() {
571 let iter: Vec<_> = TABLES.iter().collect();
572 assert_eq!(
573 iter,
574 vec![
575 (&Color::Green, &"Green"),
576 (&Color::Red, &"Red"),
577 (&Color::Blue, &"Blue")
578 ]
579 );
580 }
581
582 #[test]
583 fn iter_mut() {
584 let mut table = TABLES;
585 for (key, value) in table.iter_mut() {
586 *value = match key {
587 Color::Red => "Changed Red",
588 Color::Green => "Changed Green",
589 Color::Blue => "Changed Blue",
590 };
591 }
592 let iter: Vec<_> = table.iter().collect();
593 assert_eq!(
594 iter,
595 vec![
596 (&Color::Green, &"Changed Green"),
597 (&Color::Red, &"Changed Red"),
598 (&Color::Blue, &"Changed Blue")
599 ]
600 );
601 }
602
603 #[test]
604 fn map() {
605 let table =
606 EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
607 Color::Red => 1,
608 Color::Green => 2,
609 Color::Blue => 3,
610 });
611
612 let doubled = table.map(|value| value * 2);
613
614 assert_eq!(doubled.get(&Color::Red), &2);
615 assert_eq!(doubled.get(&Color::Green), &4);
616 assert_eq!(doubled.get(&Color::Blue), &6);
617 }
618
619 #[test]
620 fn map_with_key() {
621 let table =
622 EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
623 Color::Red => 1,
624 Color::Green => 2,
625 Color::Blue => 3,
626 });
627
628 let mapped = table.map_with_key(|key, value| match key {
629 Color::Red => value + 10, // 1 + 10 = 11
630 Color::Green => value + 20, // 2 + 20 = 22
631 Color::Blue => value + 30, // 3 + 30 = 33
632 });
633
634 // Note: The order in the underlying table is based on discriminant value (Green, Red, Blue)
635 assert_eq!(mapped.get(&Color::Red), &11);
636 assert_eq!(mapped.get(&Color::Green), &22);
637 assert_eq!(mapped.get(&Color::Blue), &33);
638 }
639
640 #[test]
641 fn map_mut() {
642 let mut table =
643 EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
644 Color::Red => 10,
645 Color::Green => 20,
646 Color::Blue => 30,
647 });
648
649 table.map_mut(|value| *value += 5);
650
651 assert_eq!(table.get(&Color::Red), &15);
652 assert_eq!(table.get(&Color::Green), &25);
653 assert_eq!(table.get(&Color::Blue), &35);
654 }
655
656 #[test]
657 fn map_mut_with_key() {
658 let mut table =
659 EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
660 Color::Red => 10,
661 Color::Green => 20,
662 Color::Blue => 30,
663 });
664
665 table.map_mut_with_key(|key, value| {
666 *value += match key {
667 Color::Red => 1, // 10 + 1 = 11
668 Color::Green => 2, // 20 + 2 = 22
669 Color::Blue => 3, // 30 + 3 = 33
670 }
671 });
672
673 assert_eq!(table.get(&Color::Red), &11);
674 assert_eq!(table.get(&Color::Green), &22);
675 assert_eq!(table.get(&Color::Blue), &33);
676 }
677
678 macro_rules! run_variants_test {
679 ($($variant:ident),+) => {{
680 #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
681 #[repr(u8)]
682 enum Test {
683 $($variant,)*
684 }
685
686 let map = EnumTable::<Test, &'static str, { Test::COUNT }>::new_with_fn(|t| match t {
687 $(Test::$variant => stringify!($variant),)*
688 });
689 $(
690 assert_eq!(map.get(&Test::$variant), &stringify!($variant));
691 )*
692 }};
693 }
694
695 #[test]
696 fn binary_search_correct_variants() {
697 run_variants_test!(A);
698 run_variants_test!(A, B);
699 run_variants_test!(A, B, C);
700 run_variants_test!(A, B, C, D);
701 run_variants_test!(A, B, C, D, E);
702 }
703}