enum_table/builder.rs
1use core::mem::MaybeUninit;
2
3use crate::{intrinsics::to_usize, EnumTable, Enumable};
4
5/// A builder for creating an `EnumTable` with a specified number of elements.
6///
7/// `EnumTableBuilder` allows for the incremental construction of an `EnumTable`
8/// by pushing elements one by one and then building the final table.
9///
10/// # Note
11/// The builder is expected to be filled completely before building the table.
12/// If the builder is not filled completely, the `build` and `build_to` method will panic.
13/// For a clearer and more concise approach, consider using the [`crate::et`] macro.
14///
15/// # Example
16/// ```rust
17/// use enum_table::{EnumTable, Enumable, builder::EnumTableBuilder,};
18///
19/// #[derive(Debug, Enumable)]
20/// enum Test {
21/// A,
22/// B,
23/// C,
24/// }
25///
26/// const TABLE: EnumTable<Test, &'static str, { Test::COUNT }> = {
27/// let mut builder = EnumTableBuilder::<Test, &'static str, { Test::COUNT }>::new();
28/// builder.push(&Test::A, "A");
29/// builder.push(&Test::B, "B");
30/// builder.push(&Test::C, "C");
31/// builder.build_to()
32/// };
33///
34/// // Access values associated with enum variants
35/// assert_eq!(TABLE.get(&Test::A), &"A");
36/// assert_eq!(TABLE.get(&Test::B), &"B");
37/// assert_eq!(TABLE.get(&Test::C), &"C");
38/// ```
39pub struct EnumTableBuilder<K: Enumable, V, const N: usize> {
40 idx: usize,
41 table: MaybeUninit<[(usize, V); N]>,
42 _phantom: core::marker::PhantomData<K>,
43}
44
45impl<K: Enumable, V, const N: usize> EnumTableBuilder<K, V, N> {
46 /// Creates a new `EnumTableBuilder` with an uninitialized table.
47 ///
48 /// # Returns
49 ///
50 /// A new instance of `EnumTableBuilder`.
51 pub const fn new() -> Self {
52 Self {
53 idx: 0,
54 table: MaybeUninit::uninit(),
55 _phantom: core::marker::PhantomData,
56 }
57 }
58
59 /// Pushes a new element into the builder.
60 ///
61 /// # Arguments
62 ///
63 /// * `variant` - A reference to an enumeration variant.
64 /// * `value` - The value to associate with the variant.
65 pub const fn push(&mut self, variant: &K, value: V) {
66 if self.idx >= N {
67 panic!("EnumTableBuilder: too many elements pushed");
68 }
69 let element = (to_usize(variant), value);
70
71 unsafe {
72 self.table
73 .as_mut_ptr()
74 .cast::<(usize, V)>()
75 .add(self.idx)
76 .write(element);
77 }
78
79 self.idx += 1;
80 }
81
82 /// Builds the table from the pushed elements.
83 ///
84 /// # Returns
85 ///
86 /// An array of tuples where each tuple contains a discriminant of an enumeration
87 /// variant and its associated value.
88 pub const fn build(self) -> [(usize, V); N] {
89 if self.idx != N {
90 panic!("EnumTableBuilder: not enough elements");
91 }
92
93 const fn is_sorted<const N: usize, V>(arr: &[(usize, V); N]) -> bool {
94 let mut i = 0;
95 while i < N - 1 {
96 if arr[i].0 >= arr[i + 1].0 {
97 return false;
98 }
99 i += 1;
100 }
101 true
102 }
103
104 // SAFETY: The table is filled.
105 let table = unsafe { self.table.assume_init() };
106
107 debug_assert!(
108 is_sorted(&table),
109 "EnumTableBuilder: elements are not sorted by discriminant. Ensure that the elements are pushed in the correct order."
110 );
111
112 table
113 }
114
115 /// Builds the `EnumTable` from the pushed elements.
116 ///
117 /// # Returns
118 ///
119 /// An `EnumTable` containing the elements pushed into the builder.
120 pub const fn build_to(self) -> EnumTable<K, V, N> {
121 EnumTable::new(self.build())
122 }
123
124 /// Returns the number of elements the builder is expected to hold.
125 ///
126 /// # Returns
127 ///
128 /// The number of elements `N`.
129 pub const fn len(&self) -> usize {
130 N
131 }
132
133 /// Returns `true` if the builder has no elements pushed yet.
134 ///
135 /// # Returns
136 ///
137 /// `true` if no elements have been pushed, `false` otherwise.
138 pub const fn is_empty(&self) -> bool {
139 self.idx == 0
140 }
141}
142
143impl<K: Enumable, V, const N: usize> Default for EnumTableBuilder<K, V, N> {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn builder() {
155 #[derive(Enumable)]
156 enum Test {
157 A,
158 B,
159 C,
160 }
161
162 const TABLE: EnumTable<Test, &'static str, { Test::COUNT }> = {
163 let mut builder = EnumTableBuilder::<Test, &'static str, { Test::COUNT }>::new();
164
165 let mut i = 0;
166 while i < builder.len() {
167 let t = &Test::VARIANTS[i];
168 builder.push(
169 t,
170 match t {
171 Test::A => "A",
172 Test::B => "B",
173 Test::C => "C",
174 },
175 );
176 i += 1;
177 }
178
179 builder.build_to()
180 };
181
182 assert_eq!(TABLE.get(&Test::A), &"A");
183 assert_eq!(TABLE.get(&Test::B), &"B");
184 assert_eq!(TABLE.get(&Test::C), &"C");
185 }
186}