Skip to main content

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}