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