enum_table/builder.rs
1use core::mem::MaybeUninit;
2
3use crate::{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, Copy, Clone, 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/// unsafe {
29/// builder.push_unchecked(&Test::A, "A");
30/// builder.push_unchecked(&Test::B, "B");
31/// builder.push_unchecked(&Test::C, "C");
32/// builder.build_to_unchecked()
33/// }
34/// };
35///
36/// // Access values associated with enum variants
37/// assert_eq!(TABLE.get(&Test::A), &"A");
38/// assert_eq!(TABLE.get(&Test::B), &"B");
39/// assert_eq!(TABLE.get(&Test::C), &"C");
40/// ```
41pub struct EnumTableBuilder<K: Enumable, V, const N: usize> {
42 idx: usize,
43 table: MaybeUninit<[(K, V); N]>,
44}
45
46impl<K: Enumable, V, const N: usize> EnumTableBuilder<K, V, N> {
47 /// Creates a new `EnumTableBuilder` with an uninitialized table.
48 ///
49 /// # Returns
50 ///
51 /// A new instance of `EnumTableBuilder`.
52 pub const fn new() -> Self {
53 Self {
54 idx: 0,
55 table: MaybeUninit::uninit(),
56 }
57 }
58
59 /// Pushes a new element into the builder without safety checks.
60 ///
61 /// # Safety
62 ///
63 /// * The caller must ensure that elements are pushed in the correct order
64 /// (sorted by discriminant).
65 /// * The caller must ensure that no variant is pushed more than once.
66 /// * The caller must ensure that the builder doesn't exceed capacity N.
67 ///
68 /// # Arguments
69 ///
70 /// * `variant` - A reference to an enumeration variant.
71 /// * `value` - The value to associate with the variant.
72 pub const unsafe fn push_unchecked(&mut self, variant: &K, value: V) {
73 debug_assert!(self.idx < N, "EnumTableBuilder: too many elements pushed");
74
75 let element = (*variant, value);
76
77 unsafe {
78 self.table
79 .as_mut_ptr()
80 .cast::<(K, V)>()
81 .add(self.idx)
82 .write(element);
83 }
84
85 self.idx += 1;
86 }
87
88 /// Builds the table from the pushed elements without checking if all variants are filled.
89 ///
90 /// # Safety
91 ///
92 /// The caller must ensure that all N variants have been pushed to the builder.
93 /// If this is not the case, the resulting table will contain uninitialized memory.
94 ///
95 /// # Returns
96 ///
97 /// An array of tuples where each tuple contains an enumeration
98 /// variant and its associated value.
99 pub const unsafe fn build_unchecked(self) -> [(K, V); N] {
100 #[cfg(debug_assertions)]
101 assert!(
102 self.idx == N,
103 "EnumTableBuilder: not all elements have been pushed"
104 );
105
106 #[cfg(debug_assertions)]
107 const fn is_sorted<const N: usize, K, V>(arr: &[(K, V); N]) -> bool {
108 let mut i = 0;
109 while i < N - 1 {
110 if !crate::intrinsics::const_enum_lt(&arr[i].0, &arr[i + 1].0) {
111 return false;
112 }
113 i += 1;
114 }
115 true
116 }
117
118 // SAFETY: Caller guarantees that the table is filled.
119 let table = unsafe { self.table.assume_init() };
120
121 #[cfg(debug_assertions)]
122 assert!(
123 is_sorted(&table),
124 "EnumTableBuilder: elements are not sorted by discriminant. Ensure that the elements are pushed in the correct order."
125 );
126
127 table
128 }
129
130 /// Builds the `EnumTable` from the pushed elements without checking if all variants are filled.
131 ///
132 /// # Safety
133 ///
134 /// The caller must ensure that all N variants have been pushed to the builder.
135 ///
136 /// # Returns
137 ///
138 /// An `EnumTable` containing the elements pushed into the builder.
139 pub const unsafe fn build_to_unchecked(self) -> EnumTable<K, V, N> {
140 EnumTable::new(unsafe { self.build_unchecked() })
141 }
142
143 /// Returns the number of elements pushed into the builder.
144 pub const fn len(&self) -> usize {
145 self.idx
146 }
147
148 /// Returns the capacity of the builder.
149 pub const fn capacity(&self) -> usize {
150 N
151 }
152
153 /// Returns `true` if the builder has no elements pushed yet.
154 ///
155 /// # Returns
156 ///
157 /// `true` if no elements have been pushed, `false` otherwise.
158 pub const fn is_empty(&self) -> bool {
159 self.idx == 0
160 }
161}
162
163impl<K: Enumable, V, const N: usize> Default for EnumTableBuilder<K, V, N> {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn builder() {
175 #[derive(Debug, Clone, Copy, PartialEq, Eq, Enumable)]
176 enum Test {
177 A,
178 B,
179 C,
180 }
181
182 const TABLE: EnumTable<Test, &'static str, { Test::COUNT }> = {
183 let mut builder = EnumTableBuilder::<Test, &'static str, { Test::COUNT }>::new();
184
185 let mut i = 0;
186 while i < builder.capacity() {
187 let t = &Test::VARIANTS[i];
188 unsafe {
189 builder.push_unchecked(
190 t,
191 match t {
192 Test::A => "A",
193 Test::B => "B",
194 Test::C => "C",
195 },
196 );
197 }
198 i += 1;
199 }
200
201 unsafe { builder.build_to_unchecked() }
202 };
203
204 assert_eq!(TABLE.get(&Test::A), &"A");
205 assert_eq!(TABLE.get(&Test::B), &"B");
206 assert_eq!(TABLE.get(&Test::C), &"C");
207 }
208}