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