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