Skip to main content

enum_table/impls/
checked_from_pairs.rs

1use crate::{EnumTable, Enumerable, intrinsics};
2
3impl<K: Enumerable, V, const N: usize> EnumTable<K, V, N> {
4    /// Creates a new `EnumTable` from `pairs`, or returns `None` if `pairs` doesn't
5    /// contain exactly one entry for each variant of `K`.
6    ///
7    /// # Examples
8    ///
9    /// ```rust
10    /// use enum_table::{EnumTable, Enumerable};
11    ///
12    /// #[derive(Enumerable, Copy, Clone, Debug, PartialEq)]
13    /// enum Color {
14    ///     Red,
15    ///     Green,
16    ///     Blue,
17    /// }
18    ///
19    /// let pairs = [
20    ///     (Color::Red, "Red"),
21    ///     (Color::Green, "Green"),
22    ///     (Color::Blue, "Blue"),
23    /// ];
24    /// let table = EnumTable::<Color, &str, { Color::COUNT }>::checked_from_pairs(pairs).unwrap();
25    /// assert_eq!(table.get(Color::Red), &"Red");
26    /// assert_eq!(table.get(Color::Green), &"Green");
27    /// assert_eq!(table.get(Color::Blue), &"Blue");
28    /// ```
29    ///
30    /// A duplicate entry is rejected, instead of silently producing a table with an
31    /// unrelated variant missing:
32    ///
33    /// ```rust
34    /// use enum_table::{EnumTable, Enumerable};
35    ///
36    /// #[derive(Enumerable, Copy, Clone, Debug, PartialEq)]
37    /// enum Color {
38    ///     Red,
39    ///     Green,
40    ///     Blue,
41    /// }
42    ///
43    /// let pairs = [
44    ///     (Color::Red, "Red"),
45    ///     (Color::Green, "Green"),
46    ///     (Color::Red, "Duplicate Red"),
47    /// ];
48    /// assert_eq!(
49    ///     EnumTable::<Color, &str, { Color::COUNT }>::checked_from_pairs(pairs),
50    ///     None
51    /// );
52    /// ```
53    pub fn checked_from_pairs(
54        pairs: impl IntoIterator<Item = (K, V), IntoIter: ExactSizeIterator>,
55    ) -> Option<Self> {
56        let pairs = pairs.into_iter();
57        if pairs.len() != N {
58            return None;
59        }
60
61        let mut slots: [Option<V>; N] = core::array::from_fn(|_| None);
62        for (key, value) in pairs {
63            let idx = key.variant_index();
64            if slots[idx].is_some() {
65                return None;
66            }
67            slots[idx] = Some(value);
68        }
69
70        let table = intrinsics::try_collect_array(|i| slots[i].take().ok_or(())).ok()?;
71        Some(Self::new(table))
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumerable)]
80    enum Color {
81        Red = 33,
82        Green = 11,
83        Blue = 222,
84    }
85
86    #[test]
87    fn checked_from_pairs() {
88        let pairs = [
89            (Color::Red, "Red"),
90            (Color::Green, "Green"),
91            (Color::Blue, "Blue"),
92        ];
93
94        let table = EnumTable::<Color, &str, { Color::COUNT }>::checked_from_pairs(pairs).unwrap();
95        assert_eq!(table.get(Color::Red), &"Red");
96        assert_eq!(table.get(Color::Green), &"Green");
97        assert_eq!(table.get(Color::Blue), &"Blue");
98    }
99
100    #[test]
101    fn checked_from_pairs_wrong_len() {
102        let pairs = [(Color::Red, "Red"), (Color::Green, "Green")];
103        assert_eq!(
104            EnumTable::<Color, &str, { Color::COUNT }>::checked_from_pairs(pairs),
105            None
106        );
107    }
108
109    #[test]
110    fn checked_from_pairs_duplicate_key() {
111        let pairs = [
112            (Color::Red, "Red"),
113            (Color::Green, "Green"),
114            (Color::Red, "Duplicate Red"),
115        ];
116        assert_eq!(
117            EnumTable::<Color, &str, { Color::COUNT }>::checked_from_pairs(pairs),
118            None
119        );
120    }
121
122    #[test]
123    fn checked_from_pairs_missing_variant() {
124        let pairs = [(Color::Red, "Red"), (Color::Green, "Green")];
125        // 2 pairs against N=3 is caught by the length check before the missing-variant
126        // path is ever reached, so exercise it via an iterator whose (inaccurate)
127        // `ExactSizeIterator::len` matches `N` but whose actual items still leave a
128        // variant unfilled.
129        struct LyingLen<I>(I, usize);
130        impl<I: Iterator> Iterator for LyingLen<I> {
131            type Item = I::Item;
132            fn next(&mut self) -> Option<Self::Item> {
133                self.0.next()
134            }
135        }
136        impl<I: Iterator> ExactSizeIterator for LyingLen<I> {
137            fn len(&self) -> usize {
138                self.1
139            }
140        }
141
142        let iter = LyingLen(pairs.into_iter(), 3);
143        assert_eq!(
144            EnumTable::<Color, &str, { Color::COUNT }>::checked_from_pairs(iter),
145            None
146        );
147    }
148}