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;
17mod macros;
18
19use intrinsics::{copy_from_usize, copy_variant, from_usize, to_usize};
20
21pub trait Enumable: Sized + 'static {
26 const VARIANTS: &'static [Self];
27 const COUNT: usize = Self::VARIANTS.len();
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum EnumTableFromVecError<K> {
33 InvalidSize { expected: usize, found: usize },
35 MissingVariant(K),
38}
39
40impl<K: core::fmt::Debug> core::fmt::Display for EnumTableFromVecError<K> {
41 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42 match self {
43 EnumTableFromVecError::InvalidSize { expected, found } => {
44 write!(f, "Invalid vector size: expected {expected}, found {found}")
45 }
46 EnumTableFromVecError::MissingVariant(variant) => {
47 write!(f, "Missing enum variant: {variant:?}")
48 }
49 }
50 }
51}
52
53impl<K: core::fmt::Debug> core::error::Error for EnumTableFromVecError<K> {}
54
55pub struct EnumTable<K: Enumable, V, const N: usize> {
102 table: [(usize, V); N],
103 _phantom: core::marker::PhantomData<K>,
104}
105
106impl<K: Enumable, V, const N: usize> EnumTable<K, V, N> {
107 pub(crate) const fn new(table: [(usize, V); N]) -> Self {
110 #[cfg(debug_assertions)]
111 const {
112 if !intrinsics::is_sorted(K::VARIANTS) {
115 panic!("Enumable: variants are not sorted by discriminant. Use `enum_table::Enumable` derive macro to ensure correct ordering.");
116 }
117 }
118
119 Self {
120 table,
121 _phantom: core::marker::PhantomData,
122 }
123 }
124
125 pub fn new_with_fn(mut f: impl FnMut(&K) -> V) -> Self {
134 let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
135
136 for variant in K::VARIANTS {
137 builder.push(variant, f(variant));
138 }
139
140 builder.build_to()
141 }
142
143 pub fn try_new_with_fn<E>(mut f: impl FnMut(&K) -> Result<V, E>) -> Result<Self, (K, E)> {
159 let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
160
161 for variant in K::VARIANTS {
162 match f(variant) {
163 Ok(value) => builder.push(variant, value),
164 Err(e) => return Err((copy_variant(variant), e)),
165 }
166 }
167
168 Ok(builder.build_to())
169 }
170
171 pub fn checked_new_with_fn(mut f: impl FnMut(&K) -> Option<V>) -> Result<Self, K> {
187 let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
188
189 for variant in K::VARIANTS {
190 if let Some(value) = f(variant) {
191 builder.push(variant, value);
192 } else {
193 return Err(copy_variant(variant));
194 }
195 }
196
197 Ok(builder.build_to())
198 }
199
200 pub(crate) const fn binary_search(&self, variant: &K) -> usize {
201 let discriminant = to_usize(variant);
202 let mut low = 0;
203 let mut high = N;
204
205 while low < high {
206 let mid = low + (high - low) / 2;
207 if self.table[mid].0 < discriminant {
208 low = mid + 1;
209 } else {
210 high = mid;
211 }
212 }
213
214 low
215 }
216
217 pub const fn get(&self, variant: &K) -> &V {
223 let idx = self.binary_search(variant);
224 &self.table[idx].1
225 }
226
227 pub const fn get_mut(&mut self, variant: &K) -> &mut V {
233 let idx = self.binary_search(variant);
234 &mut self.table[idx].1
235 }
236
237 pub const fn set(&mut self, variant: &K, value: V) -> V {
246 let idx = self.binary_search(variant);
247 core::mem::replace(&mut self.table[idx].1, value)
248 }
249
250 pub const fn len(&self) -> usize {
252 N
253 }
254
255 pub const fn is_empty(&self) -> bool {
257 false
258 }
259
260 pub fn keys(&self) -> impl Iterator<Item = &K> {
262 self.table
263 .iter()
264 .map(|(discriminant, _)| from_usize(discriminant))
265 }
266
267 pub fn values(&self) -> impl Iterator<Item = &V> {
269 self.table.iter().map(|(_, value)| value)
270 }
271
272 pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
274 self.table.iter_mut().map(|(_, value)| value)
275 }
276
277 pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
279 self.table
280 .iter()
281 .map(|(discriminant, value)| (from_usize(discriminant), value))
282 }
283
284 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
286 self.table
287 .iter_mut()
288 .map(|(discriminant, value)| (from_usize(discriminant), value))
289 }
290
291 pub fn map<U, F>(self, mut f: F) -> EnumTable<K, U, N>
321 where
322 F: FnMut(V) -> U,
323 {
324 let mut builder = builder::EnumTableBuilder::<K, U, N>::new();
325
326 for (discriminant, value) in self.table {
327 let key = from_usize(&discriminant);
328 builder.push(key, f(value));
329 }
330
331 builder.build_to()
332 }
333
334 pub fn into_vec(self) -> Vec<(K, V)> {
361 self.table
362 .into_iter()
363 .map(|(discriminant, value)| (copy_from_usize(&discriminant), value))
364 .collect()
365 }
366
367 pub fn try_from_vec(mut vec: Vec<(K, V)>) -> Result<Self, EnumTableFromVecError<K>> {
399 if vec.len() != N {
400 return Err(EnumTableFromVecError::InvalidSize {
401 expected: N,
402 found: vec.len(),
403 });
404 }
405
406 let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
407
408 for variant in K::VARIANTS {
410 if let Some(pos) = vec
411 .iter()
412 .position(|(k, _)| to_usize(k) == to_usize(variant))
413 {
414 let (_, value) = vec.swap_remove(pos);
415 builder.push(variant, value);
416 } else {
417 return Err(EnumTableFromVecError::MissingVariant(copy_variant(variant)));
418 }
419 }
420
421 Ok(builder.build_to())
422 }
423}
424
425impl<K: Enumable, V, const N: usize> EnumTable<K, Option<V>, N> {
426 pub const fn new_fill_with_none() -> Self {
428 let mut builder = builder::EnumTableBuilder::<K, Option<V>, N>::new();
429
430 let mut i = 0;
431 while i < N {
432 let variant = &K::VARIANTS[i];
433 builder.push(variant, None);
434 i += 1;
435 }
436
437 builder.build_to()
438 }
439
440 pub fn clear_to_none(&mut self) {
442 for (_, value) in &mut self.table {
443 *value = None;
444 }
445 }
446}
447
448impl<K: Enumable, V: Copy, const N: usize> EnumTable<K, V, N> {
449 pub const fn new_fill_with_copy(value: V) -> Self {
450 let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
451
452 let mut i = 0;
453 while i < N {
454 let variant = &K::VARIANTS[i];
455 builder.push(variant, value);
456 i += 1;
457 }
458
459 builder.build_to()
460 }
461}
462
463impl<K: Enumable, V: Default, const N: usize> EnumTable<K, V, N> {
464 pub fn new_fill_with_default() -> Self {
469 let mut builder = builder::EnumTableBuilder::<K, V, N>::new();
470
471 for variant in K::VARIANTS {
472 builder.push(variant, V::default());
473 }
474
475 builder.build_to()
476 }
477
478 pub fn clear_to_default(&mut self) {
480 for (_, value) in &mut self.table {
481 *value = V::default();
482 }
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489
490 #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
491 enum Color {
492 Red = 33,
493 Green = 11,
494 Blue = 222,
495 }
496
497 const TABLES: EnumTable<Color, &'static str, { Color::COUNT }> =
498 crate::et!(Color, &'static str, |color| match color {
499 Color::Red => "Red",
500 Color::Green => "Green",
501 Color::Blue => "Blue",
502 });
503
504 #[test]
505 fn new_with_fn() {
506 let table =
507 EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
508 Color::Red => "Red",
509 Color::Green => "Green",
510 Color::Blue => "Blue",
511 });
512
513 assert_eq!(table.get(&Color::Red), &"Red");
514 assert_eq!(table.get(&Color::Green), &"Green");
515 assert_eq!(table.get(&Color::Blue), &"Blue");
516 }
517
518 #[test]
519 fn try_new_with_fn() {
520 let table =
521 EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
522 |color| match color {
523 Color::Red => Ok::<&'static str, core::convert::Infallible>("Red"),
524 Color::Green => Ok("Green"),
525 Color::Blue => Ok("Blue"),
526 },
527 );
528
529 assert!(table.is_ok());
530 let table = table.unwrap();
531
532 assert_eq!(table.get(&Color::Red), &"Red");
533 assert_eq!(table.get(&Color::Green), &"Green");
534 assert_eq!(table.get(&Color::Blue), &"Blue");
535
536 let error_table = EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
537 |color| match color {
538 Color::Red => Ok("Red"),
539 Color::Green => Err("Error on Green"),
540 Color::Blue => Ok("Blue"),
541 },
542 );
543
544 assert!(error_table.is_err());
545 let (variant, error) = error_table.unwrap_err();
546
547 assert_eq!(variant, Color::Green);
548 assert_eq!(error, "Error on Green");
549 }
550
551 #[test]
552 fn checked_new_with_fn() {
553 let table =
554 EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
555 match color {
556 Color::Red => Some("Red"),
557 Color::Green => Some("Green"),
558 Color::Blue => Some("Blue"),
559 }
560 });
561
562 assert!(table.is_ok());
563 let table = table.unwrap();
564
565 assert_eq!(table.get(&Color::Red), &"Red");
566 assert_eq!(table.get(&Color::Green), &"Green");
567 assert_eq!(table.get(&Color::Blue), &"Blue");
568
569 let error_table =
570 EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
571 match color {
572 Color::Red => Some("Red"),
573 Color::Green => None,
574 Color::Blue => Some("Blue"),
575 }
576 });
577
578 assert!(error_table.is_err());
579 let variant = error_table.unwrap_err();
580
581 assert_eq!(variant, Color::Green);
582 }
583
584 #[test]
585 fn get() {
586 assert_eq!(TABLES.get(&Color::Red), &"Red");
587 assert_eq!(TABLES.get(&Color::Green), &"Green");
588 assert_eq!(TABLES.get(&Color::Blue), &"Blue");
589 }
590
591 #[test]
592 fn get_mut() {
593 let mut table = TABLES;
594 assert_eq!(table.get_mut(&Color::Red), &mut "Red");
595 assert_eq!(table.get_mut(&Color::Green), &mut "Green");
596 assert_eq!(table.get_mut(&Color::Blue), &mut "Blue");
597
598 *table.get_mut(&Color::Red) = "Changed Red";
599 *table.get_mut(&Color::Green) = "Changed Green";
600 *table.get_mut(&Color::Blue) = "Changed Blue";
601
602 assert_eq!(table.get(&Color::Red), &"Changed Red");
603 assert_eq!(table.get(&Color::Green), &"Changed Green");
604 assert_eq!(table.get(&Color::Blue), &"Changed Blue");
605 }
606
607 #[test]
608 fn set() {
609 let mut table = TABLES;
610 assert_eq!(table.set(&Color::Red, "New Red"), "Red");
611 assert_eq!(table.set(&Color::Green, "New Green"), "Green");
612 assert_eq!(table.set(&Color::Blue, "New Blue"), "Blue");
613
614 assert_eq!(table.get(&Color::Red), &"New Red");
615 assert_eq!(table.get(&Color::Green), &"New Green");
616 assert_eq!(table.get(&Color::Blue), &"New Blue");
617 }
618
619 #[test]
620 fn keys() {
621 let keys: Vec<_> = TABLES.keys().collect();
622 assert_eq!(keys, vec![&Color::Green, &Color::Red, &Color::Blue]);
623 }
624
625 #[test]
626 fn values() {
627 let values: Vec<_> = TABLES.values().collect();
628 assert_eq!(values, vec![&"Green", &"Red", &"Blue"]);
629 }
630
631 #[test]
632 fn iter() {
633 let iter: Vec<_> = TABLES.iter().collect();
634 assert_eq!(
635 iter,
636 vec![
637 (&Color::Green, &"Green"),
638 (&Color::Red, &"Red"),
639 (&Color::Blue, &"Blue")
640 ]
641 );
642 }
643
644 #[test]
645 fn iter_mut() {
646 let mut table = TABLES;
647 for (key, value) in table.iter_mut() {
648 *value = match key {
649 Color::Red => "Changed Red",
650 Color::Green => "Changed Green",
651 Color::Blue => "Changed Blue",
652 };
653 }
654 let iter: Vec<_> = table.iter().collect();
655 assert_eq!(
656 iter,
657 vec![
658 (&Color::Green, &"Changed Green"),
659 (&Color::Red, &"Changed Red"),
660 (&Color::Blue, &"Changed Blue")
661 ]
662 );
663 }
664
665 #[test]
666 fn map() {
667 let table = EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
668 Color::Red => 1,
669 Color::Green => 2,
670 Color::Blue => 3,
671 });
672
673 let doubled = table.map(|x| x * 2);
674 assert_eq!(doubled.get(&Color::Red), &2);
675 assert_eq!(doubled.get(&Color::Green), &4);
676 assert_eq!(doubled.get(&Color::Blue), &6);
677 }
678
679 #[test]
680 fn into_vec() {
681 let table = TABLES;
682 let vec = table.into_vec();
683
684 assert_eq!(vec.len(), 3);
685 assert!(vec.contains(&(Color::Red, "Red")));
686 assert!(vec.contains(&(Color::Green, "Green")));
687 assert!(vec.contains(&(Color::Blue, "Blue")));
688 }
689
690 #[test]
691 fn try_from_vec() {
692 let vec = vec![
693 (Color::Red, "Red"),
694 (Color::Green, "Green"),
695 (Color::Blue, "Blue"),
696 ];
697
698 let table = EnumTable::<Color, &str, { Color::COUNT }>::try_from_vec(vec).unwrap();
699 assert_eq!(table.get(&Color::Red), &"Red");
700 assert_eq!(table.get(&Color::Green), &"Green");
701 assert_eq!(table.get(&Color::Blue), &"Blue");
702 }
703
704 #[test]
705 fn try_from_vec_invalid_size() {
706 let vec = vec![
707 (Color::Red, "Red"),
708 (Color::Green, "Green"),
709 ];
711
712 let result = EnumTable::<Color, &str, { Color::COUNT }>::try_from_vec(vec);
713 assert_eq!(
714 result,
715 Err(crate::EnumTableFromVecError::InvalidSize {
716 expected: 3,
717 found: 2
718 })
719 );
720 }
721
722 #[test]
723 fn try_from_vec_missing_variant() {
724 let vec = vec![
725 (Color::Red, "Red"),
726 (Color::Green, "Green"),
727 (Color::Red, "Duplicate Red"), ];
729
730 let result = EnumTable::<Color, &str, { Color::COUNT }>::try_from_vec(vec);
731 assert_eq!(
732 result,
733 Err(crate::EnumTableFromVecError::MissingVariant(Color::Blue))
734 );
735 }
736
737 #[test]
738 fn conversion_roundtrip() {
739 let original = TABLES;
740 let vec = original.into_vec();
741 let reconstructed = EnumTable::<Color, &str, { Color::COUNT }>::try_from_vec(vec).unwrap();
742
743 assert_eq!(reconstructed.get(&Color::Red), &"Red");
744 assert_eq!(reconstructed.get(&Color::Green), &"Green");
745 assert_eq!(reconstructed.get(&Color::Blue), &"Blue");
746 }
747
748 macro_rules! run_variants_test {
749 ($($variant:ident),+) => {{
750 #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
751 #[repr(u8)]
752 enum Test {
753 $($variant,)*
754 }
755
756 let map = EnumTable::<Test, &'static str, { Test::COUNT }>::new_with_fn(|t| match t {
757 $(Test::$variant => stringify!($variant),)*
758 });
759 $(
760 assert_eq!(map.get(&Test::$variant), &stringify!($variant));
761 )*
762 }};
763 }
764
765 #[test]
766 fn binary_search_correct_variants() {
767 run_variants_test!(A);
768 run_variants_test!(A, B);
769 run_variants_test!(A, B, C);
770 run_variants_test!(A, B, C, D);
771 run_variants_test!(A, B, C, D, E);
772 }
773}