enum_table/lib.rs
1#![doc = include_str!(concat!("../", core::env!("CARGO_PKG_README")))]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4#[cfg(test)]
5pub extern crate self as enum_table;
6
7use core::marker::PhantomData;
8
9#[cfg(feature = "derive")]
10pub use enum_table_derive::Enumable;
11
12pub mod builder;
13mod intrinsics;
14
15pub mod __private {
16 pub use crate::intrinsics::{sort_variants, variant_index_of};
17}
18
19mod impls;
20#[allow(unused_imports)]
21pub use impls::*;
22
23mod macros;
24
25/// A trait for enumerations that can be used with `EnumTable`.
26///
27/// This trait requires that the enumeration provides a static array of its variants
28/// and a constant representing the count of these variants.
29///
30/// # Safety
31///
32/// The implementations of this trait rely on the memory layout of the enum.
33/// It is strongly recommended to use a primitive representation (e.g., `#[repr(u8)]`)
34/// to ensure that the enum has no padding bytes and a stable layout.
35///
36/// **Note on Padding:** If the enum contains padding bytes (e.g., `#[repr(u8, align(2))]`),
37/// it will cause a **compile-time error** during constant evaluation, as Rust's
38/// constant evaluator does not allow reading uninitialized memory (padding).
39pub trait Enumable: Copy + 'static {
40 const VARIANTS: &'static [Self];
41 const COUNT: usize = Self::VARIANTS.len();
42
43 /// Returns the index of this variant in the sorted `VARIANTS` array.
44 ///
45 /// When derived via `#[derive(Enumable)]`, this is O(1) at runtime
46 /// (using compile-time-computed constants). The default implementation
47 /// falls back to O(log N) binary search for manual implementations.
48 fn variant_index(&self) -> usize {
49 intrinsics::binary_search_index::<Self>(self)
50 }
51}
52
53/// A table that associates each variant of an enumeration with a value.
54///
55/// `EnumTable` is a generic struct that uses an enumeration as keys and stores
56/// associated values. It provides efficient constant-time access (O(1))
57/// to the values based on the enumeration variant. This is particularly useful
58/// when you want to map enum variants to specific values without the overhead
59/// of a `HashMap`.
60///
61/// # Guarantees and Design
62///
63/// The core design principle of `EnumTable` is that an instance is guaranteed to hold a
64/// value for every variant of the enum `K`. This guarantee allows for a cleaner API
65/// than general-purpose map structures.
66///
67/// For example, the [`Self::get`] method returns `&V` directly. This is in contrast to
68/// [`std::collections::HashMap::get`], which returns an `Option<&V>` because a key may or may not be
69/// present. With `EnumTable`, the presence of all keys (variants) is a type-level
70/// invariant, eliminating the need for `unwrap()` or other `Option` handling.
71///
72/// If you need to handle cases where a value might not be present or will be set
73/// later, you can use `Option<V>` as the value type: `EnumTable<K, Option<V>, N>`.
74/// The struct provides convenient methods like [`Self::new_fill_with_none`] for this pattern.
75///
76/// # Type Parameters
77///
78/// * `K`: The enumeration type that implements the `Enumable` trait. This trait
79/// ensures that the enum provides a static array of its variants and a count
80/// of these variants.
81/// * `V`: The type of values to be associated with each enum variant.
82/// * `N`: The number of variants in the enum, which should match the length of
83/// the static array of variants provided by the `Enumable` trait.
84///
85/// # Examples
86///
87/// ```rust
88/// use enum_table::{EnumTable, Enumable};
89///
90/// #[derive(Enumable, Copy, Clone)]
91/// enum Color {
92/// Red,
93/// Green,
94/// Blue,
95/// }
96///
97/// // Create an EnumTable using the new_with_fn method
98/// let table = EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
99/// Color::Red => "Red",
100/// Color::Green => "Green",
101/// Color::Blue => "Blue",
102/// });
103///
104/// // Access values associated with enum variants
105/// assert_eq!(table.get(&Color::Red), &"Red");
106/// assert_eq!(table.get(&Color::Green), &"Green");
107/// assert_eq!(table.get(&Color::Blue), &"Blue");
108/// ```
109pub struct EnumTable<K: Enumable, V, const N: usize> {
110 table: [V; N],
111 _phantom: PhantomData<K>,
112}
113
114impl<K: Enumable, V, const N: usize> EnumTable<K, V, N> {
115 /// Creates a new `EnumTable` with the given table of variants and values.
116 /// Typically, you would use the [`crate::et`] macro or [`crate::builder::EnumTableBuilder`] to create an `EnumTable`.
117 pub(crate) const fn new(table: [V; N]) -> Self {
118 const {
119 assert!(
120 N == K::COUNT,
121 "EnumTable: N must equal K::COUNT. The const generic N does not match the number of enum variants."
122 );
123
124 // Ensure that the variants are sorted by their discriminants.
125 // This is a compile-time check to ensure that the variants are in the correct order.
126 if !intrinsics::is_sorted(K::VARIANTS) {
127 panic!(
128 "Enumable: variants are not sorted by discriminant. Use `enum_table::Enumable` derive macro to ensure correct ordering."
129 );
130 }
131 }
132
133 Self {
134 table,
135 _phantom: PhantomData,
136 }
137 }
138
139 /// Creates a new `EnumTable` using a function to generate values for each variant.
140 ///
141 /// If you want to define it in a `const` context, use the [`crate::et`] macro instead.
142 ///
143 /// # Arguments
144 ///
145 /// * `f` - A function that takes a reference to an enumeration variant and returns
146 /// a value to be associated with that variant.
147 pub fn new_with_fn(mut f: impl FnMut(&K) -> V) -> Self {
148 Self::new(core::array::from_fn(|i| f(&K::VARIANTS[i])))
149 }
150
151 /// Creates a new `EnumTable` using a function that returns a `Result` for each variant.
152 ///
153 /// This method applies the provided closure to each variant of the enum. If the closure
154 /// returns `Ok(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
155 /// If the closure returns `Err(e)` for any variant, the construction is aborted and
156 /// `Err((variant, e))` is returned, where `variant` is the enum variant that caused the error.
157 ///
158 /// # Arguments
159 ///
160 /// * `f` - A closure that takes a reference to an enum variant and returns a `Result<V, E>`.
161 ///
162 /// # Returns
163 ///
164 /// * `Ok(Self)` if all variants succeed.
165 /// * `Err((variant, e))` if any variant fails, containing the failing variant and the error.
166 pub fn try_new_with_fn<E>(mut f: impl FnMut(&K) -> Result<V, E>) -> Result<Self, (K, E)> {
167 let table = intrinsics::try_collect_array(|i| {
168 let variant = &K::VARIANTS[i];
169 f(variant).map_err(|e| (*variant, e))
170 })?;
171 Ok(Self::new(table))
172 }
173
174 /// Creates a new `EnumTable` using a function that returns an `Option` for each variant.
175 ///
176 /// This method applies the provided closure to each variant of the enum. If the closure
177 /// returns `Some(value)` for all variants, an `EnumTable` is constructed and returned as `Ok(Self)`.
178 /// If the closure returns `None` for any variant, the construction is aborted and
179 /// `Err(variant)` is returned, where `variant` is the enum variant that caused the failure.
180 ///
181 /// # Arguments
182 ///
183 /// * `f` - A closure that takes a reference to an enum variant and returns an `Option<V>`.
184 ///
185 /// # Returns
186 ///
187 /// * `Ok(Self)` if all variants succeed.
188 /// * `Err(variant)` if any variant fails, containing the failing variant.
189 pub fn checked_new_with_fn(mut f: impl FnMut(&K) -> Option<V>) -> Result<Self, K> {
190 let table = intrinsics::try_collect_array(|i| {
191 let variant = &K::VARIANTS[i];
192 f(variant).ok_or(*variant)
193 })?;
194 Ok(Self::new(table))
195 }
196
197 /// Returns a reference to the value associated with the given enumeration variant.
198 ///
199 /// Uses O(1) lookup via [`Enumable::variant_index`].
200 ///
201 /// # Arguments
202 ///
203 /// * `variant` - A reference to an enumeration variant.
204 pub fn get(&self, variant: &K) -> &V {
205 &self.table[variant.variant_index()]
206 }
207
208 /// Returns a mutable reference to the value associated with the given enumeration variant.
209 ///
210 /// Uses O(1) lookup via [`Enumable::variant_index`].
211 ///
212 /// # Arguments
213 ///
214 /// * `variant` - A reference to an enumeration variant.
215 pub fn get_mut(&mut self, variant: &K) -> &mut V {
216 &mut self.table[variant.variant_index()]
217 }
218
219 /// Sets the value associated with the given enumeration variant.
220 ///
221 /// Uses O(1) lookup via [`Enumable::variant_index`].
222 ///
223 /// # Arguments
224 ///
225 /// * `variant` - A reference to an enumeration variant.
226 /// * `value` - The new value to associate with the variant.
227 ///
228 /// # Returns
229 ///
230 /// The old value associated with the variant.
231 pub fn set(&mut self, variant: &K, value: V) -> V {
232 core::mem::replace(&mut self.table[variant.variant_index()], value)
233 }
234
235 /// Returns a reference to the value associated with the given enumeration variant.
236 ///
237 /// This is a `const fn` that uses binary search (O(log N)).
238 /// For O(1) access, use [`Self::get`].
239 ///
240 /// # Arguments
241 ///
242 /// * `variant` - A reference to an enumeration variant.
243 pub const fn get_const(&self, variant: &K) -> &V {
244 let idx = intrinsics::binary_search_index::<K>(variant);
245 &self.table[idx]
246 }
247
248 /// Returns a mutable reference to the value associated with the given enumeration variant.
249 ///
250 /// This is a `const fn` that uses binary search (O(log N)).
251 /// For O(1) access, use [`Self::get_mut`].
252 ///
253 /// # Arguments
254 ///
255 /// * `variant` - A reference to an enumeration variant.
256 pub const fn get_mut_const(&mut self, variant: &K) -> &mut V {
257 let idx = intrinsics::binary_search_index::<K>(variant);
258 &mut self.table[idx]
259 }
260
261 /// Sets the value associated with the given enumeration variant.
262 ///
263 /// This is a `const fn` that uses binary search (O(log N)).
264 /// For O(1) access, use [`Self::set`].
265 ///
266 /// # Arguments
267 ///
268 /// * `variant` - A reference to an enumeration variant.
269 /// * `value` - The new value to associate with the variant.
270 ///
271 /// # Returns
272 ///
273 /// The old value associated with the variant.
274 pub const fn set_const(&mut self, variant: &K, value: V) -> V {
275 let idx = intrinsics::binary_search_index::<K>(variant);
276 core::mem::replace(&mut self.table[idx], value)
277 }
278
279 /// Returns the number of entries in the table (equal to the number of enum variants).
280 pub const fn len(&self) -> usize {
281 N
282 }
283
284 /// Returns `true` if the table has no entries (i.e., the enum has no variants).
285 pub const fn is_empty(&self) -> bool {
286 N == 0
287 }
288
289 /// Returns a reference to the underlying array of values.
290 ///
291 /// Values are ordered by the sorted discriminant of the enum variants.
292 pub const fn as_slice(&self) -> &[V] {
293 &self.table
294 }
295
296 /// Returns a mutable reference to the underlying array of values.
297 ///
298 /// Values are ordered by the sorted discriminant of the enum variants.
299 pub const fn as_mut_slice(&mut self) -> &mut [V] {
300 &mut self.table
301 }
302
303 /// Consumes the table and returns the underlying array of values.
304 ///
305 /// Values are ordered by the sorted discriminant of the enum variants.
306 pub fn into_array(self) -> [V; N] {
307 self.table
308 }
309
310 /// Combines two `EnumTable`s into a new one by applying a function to each pair of values.
311 ///
312 /// # Arguments
313 ///
314 /// * `other` - Another `EnumTable` with the same key type.
315 /// * `f` - A closure that takes two values and returns a new value.
316 ///
317 /// # Examples
318 ///
319 /// ```rust
320 /// use enum_table::{EnumTable, Enumable};
321 ///
322 /// #[derive(Enumable, Copy, Clone)]
323 /// enum Stat {
324 /// Hp,
325 /// Attack,
326 /// Defense,
327 /// }
328 ///
329 /// let base = EnumTable::<Stat, i32, { Stat::COUNT }>::new_with_fn(|s| match s {
330 /// Stat::Hp => 100,
331 /// Stat::Attack => 50,
332 /// Stat::Defense => 30,
333 /// });
334 /// let bonus = EnumTable::<Stat, i32, { Stat::COUNT }>::new_with_fn(|s| match s {
335 /// Stat::Hp => 20,
336 /// Stat::Attack => 10,
337 /// Stat::Defense => 5,
338 /// });
339 ///
340 /// let total = base.zip(bonus, |a, b| a + b);
341 /// assert_eq!(total.get(&Stat::Hp), &120);
342 /// assert_eq!(total.get(&Stat::Attack), &60);
343 /// assert_eq!(total.get(&Stat::Defense), &35);
344 /// ```
345 pub fn zip<U, W>(
346 self,
347 other: EnumTable<K, U, N>,
348 mut f: impl FnMut(V, U) -> W,
349 ) -> EnumTable<K, W, N> {
350 let mut other_iter = other.table.into_iter();
351 EnumTable::new(self.table.map(|v| {
352 // SAFETY: both arrays have exactly N elements, and map calls this exactly N times
353 let u = unsafe { other_iter.next().unwrap_unchecked() };
354 f(v, u)
355 }))
356 }
357
358 /// Transforms all values in the table using the provided function.
359 ///
360 /// This method consumes the table and creates a new one with values
361 /// transformed by the given closure.
362 ///
363 /// # Arguments
364 ///
365 /// * `f` - A closure that takes an owned value and returns a new value.
366 ///
367 /// # Examples
368 ///
369 /// ```rust
370 /// use enum_table::{EnumTable, Enumable};
371 ///
372 /// #[derive(Enumable, Copy, Clone)]
373 /// enum Size {
374 /// Small,
375 /// Medium,
376 /// Large,
377 /// }
378 ///
379 /// let table = EnumTable::<Size, i32, { Size::COUNT }>::new_with_fn(|size| match size {
380 /// Size::Small => 1,
381 /// Size::Medium => 2,
382 /// Size::Large => 3,
383 /// });
384 ///
385 /// let doubled = table.map(|value| value * 2);
386 ///
387 /// assert_eq!(doubled.get(&Size::Small), &2);
388 /// assert_eq!(doubled.get(&Size::Medium), &4);
389 /// assert_eq!(doubled.get(&Size::Large), &6);
390 /// ```
391 pub fn map<U>(self, f: impl FnMut(V) -> U) -> EnumTable<K, U, N> {
392 EnumTable::new(self.table.map(f))
393 }
394
395 /// Transforms all values in the table using the provided function, with access to the key.
396 ///
397 /// This method consumes the table and creates a new one with values
398 /// transformed by the given closure, which receives both the key and the value.
399 ///
400 /// # Arguments
401 ///
402 /// * `f` - A closure that takes a key reference and an owned value, and returns a new value.
403 pub fn map_with_key<U>(self, mut f: impl FnMut(&K, V) -> U) -> EnumTable<K, U, N> {
404 let mut i = 0;
405 EnumTable::new(self.table.map(|value| {
406 let key = &K::VARIANTS[i];
407 i += 1;
408 f(key, value)
409 }))
410 }
411
412 /// Transforms all values in the table in-place using the provided function.
413 ///
414 /// # Arguments
415 ///
416 /// * `f` - A closure that takes a mutable reference to a value and modifies it.
417 ///
418 /// # Examples
419 ///
420 /// ```rust
421 /// use enum_table::{EnumTable, Enumable};
422 ///
423 /// #[derive(Enumable, Copy, Clone)]
424 /// enum Level {
425 /// Low,
426 /// Medium,
427 /// High,
428 /// }
429 ///
430 /// let mut table = EnumTable::<Level, i32, { Level::COUNT }>::new_with_fn(|level| match level {
431 /// Level::Low => 10,
432 /// Level::Medium => 20,
433 /// Level::High => 30,
434 /// });
435 ///
436 /// table.map_mut(|value| *value += 5);
437 ///
438 /// assert_eq!(table.get(&Level::Low), &15);
439 /// assert_eq!(table.get(&Level::Medium), &25);
440 /// assert_eq!(table.get(&Level::High), &35);
441 /// ```
442 pub fn map_mut(&mut self, f: impl FnMut(&mut V)) {
443 self.table.iter_mut().for_each(f);
444 }
445
446 /// Transforms all values in the table in-place using the provided function, with access to the key.
447 ///
448 /// # Arguments
449 ///
450 /// * `f` - A closure that takes a key reference and a mutable reference to a value, and modifies it.
451 pub fn map_mut_with_key(&mut self, mut f: impl FnMut(&K, &mut V)) {
452 self.table.iter_mut().enumerate().for_each(|(i, value)| {
453 f(&K::VARIANTS[i], value);
454 });
455 }
456}
457
458impl<K: Enumable, V, const N: usize> EnumTable<K, Option<V>, N> {
459 /// Creates a new `EnumTable` with `None` values for each variant.
460 pub const fn new_fill_with_none() -> Self {
461 Self::new([const { None }; N])
462 }
463
464 /// Clears the table, setting each value to `None`.
465 pub fn clear_to_none(&mut self) {
466 for value in &mut self.table {
467 *value = None;
468 }
469 }
470
471 /// Removes and returns the value associated with the given enumeration variant,
472 /// leaving `None` in its place.
473 ///
474 /// Uses O(1) lookup via [`Enumable::variant_index`].
475 ///
476 /// # Arguments
477 ///
478 /// * `variant` - A reference to an enumeration variant.
479 ///
480 /// # Returns
481 ///
482 /// The previous value, or `None` if the slot was already empty.
483 pub fn remove(&mut self, variant: &K) -> Option<V> {
484 self.table[variant.variant_index()].take()
485 }
486
487 /// Removes and returns the value associated with the given enumeration variant,
488 /// leaving `None` in its place.
489 ///
490 /// This is a `const fn` that uses binary search (O(log N)).
491 /// For O(1) access, use [`Self::remove`].
492 ///
493 /// # Arguments
494 ///
495 /// * `variant` - A reference to an enumeration variant.
496 ///
497 /// # Returns
498 ///
499 /// The previous value, or `None` if the slot was already empty.
500 pub const fn remove_const(&mut self, variant: &K) -> Option<V> {
501 let idx = intrinsics::binary_search_index::<K>(variant);
502 self.table[idx].take()
503 }
504}
505
506impl<K: Enumable, V: Copy, const N: usize> EnumTable<K, V, N> {
507 /// Creates a new `EnumTable` with the same copied value for each variant.
508 ///
509 /// This method initializes the table with the same value for each
510 /// variant of the enumeration. The value must implement `Copy`.
511 ///
512 /// # Arguments
513 ///
514 /// * `value` - The value to copy for each enum variant.
515 ///
516 /// # Examples
517 ///
518 /// ```rust
519 /// use enum_table::{EnumTable, Enumable};
520 ///
521 /// #[derive(Enumable, Copy, Clone)]
522 /// enum Status {
523 /// Active,
524 /// Inactive,
525 /// Pending,
526 /// }
527 ///
528 /// let table = EnumTable::<Status, i32, { Status::COUNT }>::new_fill_with_copy(42);
529 ///
530 /// assert_eq!(table.get(&Status::Active), &42);
531 /// assert_eq!(table.get(&Status::Inactive), &42);
532 /// assert_eq!(table.get(&Status::Pending), &42);
533 /// ```
534 pub const fn new_fill_with_copy(value: V) -> Self {
535 Self::new([value; N])
536 }
537}
538
539impl<K: Enumable, V: Default, const N: usize> EnumTable<K, V, N> {
540 /// Creates a new `EnumTable` with default values for each variant.
541 ///
542 /// This method initializes the table with the default value of type `V` for each
543 /// variant of the enumeration.
544 pub fn new_fill_with_default() -> Self {
545 Self::new(core::array::from_fn(|_| V::default()))
546 }
547
548 /// Clears the table, setting each value to its default.
549 pub fn clear_to_default(&mut self) {
550 self.table.fill_with(V::default);
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557
558 #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
559 enum Color {
560 Red = 33,
561 Green = 11,
562 Blue = 222,
563 }
564
565 const TABLES: EnumTable<Color, &'static str, { Color::COUNT }> =
566 crate::et!(Color, &'static str, |color| match color {
567 Color::Red => "Red",
568 Color::Green => "Green",
569 Color::Blue => "Blue",
570 });
571
572 #[test]
573 fn new_with_fn() {
574 let table =
575 EnumTable::<Color, &'static str, { Color::COUNT }>::new_with_fn(|color| match color {
576 Color::Red => "Red",
577 Color::Green => "Green",
578 Color::Blue => "Blue",
579 });
580
581 assert_eq!(table.get(&Color::Red), &"Red");
582 assert_eq!(table.get(&Color::Green), &"Green");
583 assert_eq!(table.get(&Color::Blue), &"Blue");
584 }
585
586 #[test]
587 fn try_new_with_fn() {
588 let table =
589 EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
590 |color| match color {
591 Color::Red => Ok::<&'static str, core::convert::Infallible>("Red"),
592 Color::Green => Ok("Green"),
593 Color::Blue => Ok("Blue"),
594 },
595 );
596
597 assert!(table.is_ok());
598 let table = table.unwrap();
599
600 assert_eq!(table.get(&Color::Red), &"Red");
601 assert_eq!(table.get(&Color::Green), &"Green");
602 assert_eq!(table.get(&Color::Blue), &"Blue");
603
604 let error_table = EnumTable::<Color, &'static str, { Color::COUNT }>::try_new_with_fn(
605 |color| match color {
606 Color::Red => Ok("Red"),
607 Color::Green => Err("Error on Green"),
608 Color::Blue => Ok("Blue"),
609 },
610 );
611
612 assert!(error_table.is_err());
613 let (variant, error) = error_table.unwrap_err();
614
615 assert_eq!(variant, Color::Green);
616 assert_eq!(error, "Error on Green");
617 }
618
619 #[test]
620 fn checked_new_with_fn() {
621 let table =
622 EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
623 match color {
624 Color::Red => Some("Red"),
625 Color::Green => Some("Green"),
626 Color::Blue => Some("Blue"),
627 }
628 });
629
630 assert!(table.is_ok());
631 let table = table.unwrap();
632
633 assert_eq!(table.get(&Color::Red), &"Red");
634 assert_eq!(table.get(&Color::Green), &"Green");
635 assert_eq!(table.get(&Color::Blue), &"Blue");
636
637 let error_table =
638 EnumTable::<Color, &'static str, { Color::COUNT }>::checked_new_with_fn(|color| {
639 match color {
640 Color::Red => Some("Red"),
641 Color::Green => None,
642 Color::Blue => Some("Blue"),
643 }
644 });
645
646 assert!(error_table.is_err());
647 let variant = error_table.unwrap_err();
648
649 assert_eq!(variant, Color::Green);
650 }
651
652 #[test]
653 fn get() {
654 assert_eq!(TABLES.get(&Color::Red), &"Red");
655 assert_eq!(TABLES.get(&Color::Green), &"Green");
656 assert_eq!(TABLES.get(&Color::Blue), &"Blue");
657 }
658
659 #[test]
660 fn get_mut() {
661 let mut table = TABLES;
662 assert_eq!(table.get_mut(&Color::Red), &mut "Red");
663 assert_eq!(table.get_mut(&Color::Green), &mut "Green");
664 assert_eq!(table.get_mut(&Color::Blue), &mut "Blue");
665
666 *table.get_mut(&Color::Red) = "Changed Red";
667 *table.get_mut(&Color::Green) = "Changed Green";
668 *table.get_mut(&Color::Blue) = "Changed Blue";
669
670 assert_eq!(table.get(&Color::Red), &"Changed Red");
671 assert_eq!(table.get(&Color::Green), &"Changed Green");
672 assert_eq!(table.get(&Color::Blue), &"Changed Blue");
673 }
674
675 #[test]
676 fn set() {
677 let mut table = TABLES;
678 assert_eq!(table.set(&Color::Red, "New Red"), "Red");
679 assert_eq!(table.set(&Color::Green, "New Green"), "Green");
680 assert_eq!(table.set(&Color::Blue, "New Blue"), "Blue");
681
682 assert_eq!(table.get(&Color::Red), &"New Red");
683 assert_eq!(table.get(&Color::Green), &"New Green");
684 assert_eq!(table.get(&Color::Blue), &"New Blue");
685 }
686
687 #[test]
688 fn keys() {
689 let keys: Vec<_> = TABLES.keys().collect();
690 assert_eq!(keys, vec![&Color::Green, &Color::Red, &Color::Blue]);
691 }
692
693 #[test]
694 fn values() {
695 let values: Vec<_> = TABLES.values().collect();
696 assert_eq!(values, vec![&"Green", &"Red", &"Blue"]);
697 }
698
699 #[test]
700 fn iter() {
701 let iter: Vec<_> = TABLES.iter().collect();
702 assert_eq!(
703 iter,
704 vec![
705 (&Color::Green, &"Green"),
706 (&Color::Red, &"Red"),
707 (&Color::Blue, &"Blue")
708 ]
709 );
710 }
711
712 #[test]
713 fn iter_mut() {
714 let mut table = TABLES;
715 for (key, value) in table.iter_mut() {
716 *value = match key {
717 Color::Red => "Changed Red",
718 Color::Green => "Changed Green",
719 Color::Blue => "Changed Blue",
720 };
721 }
722 let iter: Vec<_> = table.iter().collect();
723 assert_eq!(
724 iter,
725 vec![
726 (&Color::Green, &"Changed Green"),
727 (&Color::Red, &"Changed Red"),
728 (&Color::Blue, &"Changed Blue")
729 ]
730 );
731 }
732
733 #[test]
734 fn map() {
735 let table = EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
736 Color::Red => 1,
737 Color::Green => 2,
738 Color::Blue => 3,
739 });
740
741 let doubled = table.map(|value| value * 2);
742
743 assert_eq!(doubled.get(&Color::Red), &2);
744 assert_eq!(doubled.get(&Color::Green), &4);
745 assert_eq!(doubled.get(&Color::Blue), &6);
746 }
747
748 #[test]
749 fn map_with_key() {
750 let table = EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
751 Color::Red => 1,
752 Color::Green => 2,
753 Color::Blue => 3,
754 });
755
756 let mapped = table.map_with_key(|key, value| match key {
757 Color::Red => value + 10, // 1 + 10 = 11
758 Color::Green => value + 20, // 2 + 20 = 22
759 Color::Blue => value + 30, // 3 + 30 = 33
760 });
761
762 // Note: The order in the underlying table is based on discriminant value (Green, Red, Blue)
763 assert_eq!(mapped.get(&Color::Red), &11);
764 assert_eq!(mapped.get(&Color::Green), &22);
765 assert_eq!(mapped.get(&Color::Blue), &33);
766 }
767
768 #[test]
769 fn map_mut() {
770 let mut table =
771 EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
772 Color::Red => 10,
773 Color::Green => 20,
774 Color::Blue => 30,
775 });
776
777 table.map_mut(|value| *value += 5);
778
779 assert_eq!(table.get(&Color::Red), &15);
780 assert_eq!(table.get(&Color::Green), &25);
781 assert_eq!(table.get(&Color::Blue), &35);
782 }
783
784 #[test]
785 fn map_mut_with_key() {
786 let mut table =
787 EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|color| match color {
788 Color::Red => 10,
789 Color::Green => 20,
790 Color::Blue => 30,
791 });
792
793 table.map_mut_with_key(|key, value| {
794 *value += match key {
795 Color::Red => 1, // 10 + 1 = 11
796 Color::Green => 2, // 20 + 2 = 22
797 Color::Blue => 3, // 30 + 3 = 33
798 }
799 });
800
801 assert_eq!(table.get(&Color::Red), &11);
802 assert_eq!(table.get(&Color::Green), &22);
803 assert_eq!(table.get(&Color::Blue), &33);
804 }
805
806 macro_rules! run_variants_test {
807 ($($variant:ident),+) => {{
808 #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
809 #[repr(u8)]
810 enum Test {
811 $($variant,)*
812 }
813
814 let map = EnumTable::<Test, &'static str, { Test::COUNT }>::new_with_fn(|t| match t {
815 $(Test::$variant => stringify!($variant),)*
816 });
817 $(
818 assert_eq!(map.get(&Test::$variant), &stringify!($variant));
819 )*
820 }};
821 }
822
823 #[test]
824 fn binary_search_correct_variants() {
825 run_variants_test!(A);
826 run_variants_test!(A, B);
827 run_variants_test!(A, B, C);
828 run_variants_test!(A, B, C, D);
829 run_variants_test!(A, B, C, D, E);
830 }
831
832 #[test]
833 fn variant_index() {
834 // Color discriminants: Green=11, Red=33, Blue=222
835 // Sorted order: Green(0), Red(1), Blue(2)
836 assert_eq!(Color::Green.variant_index(), 0);
837 assert_eq!(Color::Red.variant_index(), 1);
838 assert_eq!(Color::Blue.variant_index(), 2);
839 }
840
841 #[test]
842 fn get_const() {
843 const RED: &str = TABLES.get_const(&Color::Red);
844 const GREEN: &str = TABLES.get_const(&Color::Green);
845 const BLUE: &str = TABLES.get_const(&Color::Blue);
846
847 assert_eq!(RED, "Red");
848 assert_eq!(GREEN, "Green");
849 assert_eq!(BLUE, "Blue");
850 }
851
852 #[test]
853 fn set_const() {
854 const fn make_table() -> EnumTable<Color, &'static str, { Color::COUNT }> {
855 let mut table = TABLES;
856 table.set_const(&Color::Red, "New Red");
857 table
858 }
859 const TABLE: EnumTable<Color, &'static str, { Color::COUNT }> = make_table();
860 assert_eq!(TABLE.get_const(&Color::Red), &"New Red");
861 assert_eq!(TABLE.get_const(&Color::Green), &"Green");
862 }
863
864 #[test]
865 fn get_mut_const() {
866 const fn make_table() -> EnumTable<Color, &'static str, { Color::COUNT }> {
867 let mut table = TABLES;
868 *table.get_mut_const(&Color::Green) = "Changed Green";
869 table
870 }
871 const TABLE: EnumTable<Color, &'static str, { Color::COUNT }> = make_table();
872 assert_eq!(TABLE.get_const(&Color::Green), &"Changed Green");
873 }
874
875 #[test]
876 fn remove_option() {
877 let mut table =
878 EnumTable::<Color, Option<i32>, { Color::COUNT }>::new_with_fn(|color| match color {
879 Color::Red => Some(1),
880 Color::Green => Some(2),
881 Color::Blue => None,
882 });
883
884 assert_eq!(table.remove(&Color::Red), Some(1));
885 assert_eq!(table.get(&Color::Red), &None);
886
887 assert_eq!(table.remove(&Color::Blue), None);
888 assert_eq!(table.get(&Color::Blue), &None);
889 }
890
891 #[test]
892 fn remove_const_option() {
893 const fn make_table() -> EnumTable<Color, Option<i32>, { Color::COUNT }> {
894 let mut table = EnumTable::new_fill_with_none();
895 table.set_const(&Color::Red, Some(42));
896 table.set_const(&Color::Green, Some(99));
897 table
898 }
899
900 let mut table = make_table();
901 assert_eq!(table.remove_const(&Color::Red), Some(42));
902 assert_eq!(table.get(&Color::Red), &None);
903 }
904
905 #[test]
906 fn as_slice() {
907 let slice = TABLES.as_slice();
908 assert_eq!(slice.len(), 3);
909 // Values are in sorted discriminant order: Green(11), Red(33), Blue(222)
910 assert_eq!(slice[0], "Green");
911 assert_eq!(slice[1], "Red");
912 assert_eq!(slice[2], "Blue");
913 }
914
915 #[test]
916 fn as_mut_slice() {
917 let mut table = TABLES;
918 let slice = table.as_mut_slice();
919 slice[0] = "Changed Green";
920 assert_eq!(table.get(&Color::Green), &"Changed Green");
921 }
922
923 #[test]
924 fn into_array() {
925 let arr = TABLES.into_array();
926 assert_eq!(arr, ["Green", "Red", "Blue"]);
927 }
928
929 #[test]
930 fn zip() {
931 let a = EnumTable::<Color, i32, { Color::COUNT }>::new_with_fn(|c| match c {
932 Color::Red => -10,
933 Color::Green => -20,
934 Color::Blue => -30,
935 });
936 let b = EnumTable::<Color, u32, { Color::COUNT }>::new_with_fn(|c| match c {
937 Color::Red => 1,
938 Color::Green => 2,
939 Color::Blue => 3,
940 });
941
942 let sum = a.zip(b, |x, y| (x + y as i32) as i8);
943 assert_eq!(sum.get(&Color::Red), &-9);
944 assert_eq!(sum.get(&Color::Green), &-18);
945 assert_eq!(sum.get(&Color::Blue), &-27);
946 }
947}