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}