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}