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