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}