enum_table/
builder.rs

1use core::mem::MaybeUninit;
2
3use crate::{intrinsics::to_usize, 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, 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///    builder.push(&Test::A, "A");
29///    builder.push(&Test::B, "B");
30///    builder.push(&Test::C, "C");
31///    builder.build_to()
32/// };
33///
34/// // Access values associated with enum variants
35/// assert_eq!(TABLE.get(&Test::A), &"A");
36/// assert_eq!(TABLE.get(&Test::B), &"B");
37/// assert_eq!(TABLE.get(&Test::C), &"C");
38/// ```
39pub struct EnumTableBuilder<K: Enumable, V, const N: usize> {
40    idx: usize,
41    table: MaybeUninit<[(usize, V); N]>,
42    _phantom: core::marker::PhantomData<K>,
43}
44
45impl<K: Enumable, V, const N: usize> EnumTableBuilder<K, V, N> {
46    /// Creates a new `EnumTableBuilder` with an uninitialized table.
47    ///
48    /// # Returns
49    ///
50    /// A new instance of `EnumTableBuilder`.
51    pub const fn new() -> Self {
52        Self {
53            idx: 0,
54            table: MaybeUninit::uninit(),
55            _phantom: core::marker::PhantomData,
56        }
57    }
58
59    /// Pushes a new element into the builder.
60    ///
61    /// # Arguments
62    ///
63    /// * `variant` - A reference to an enumeration variant.
64    /// * `value` - The value to associate with the variant.
65    pub const fn push(&mut self, variant: &K, value: V) {
66        if self.idx >= N {
67            panic!("EnumTableBuilder: too many elements pushed");
68        }
69        let element = (to_usize(variant), value);
70
71        unsafe {
72            self.table
73                .as_mut_ptr()
74                .cast::<(usize, V)>()
75                .add(self.idx)
76                .write(element);
77        }
78
79        self.idx += 1;
80    }
81
82    /// Builds the table from the pushed elements.
83    ///
84    /// # Returns
85    ///
86    /// An array of tuples where each tuple contains a discriminant of an enumeration
87    /// variant and its associated value.
88    pub const fn build(self) -> [(usize, V); N] {
89        if self.idx != N {
90            panic!("EnumTableBuilder: not enough elements");
91        }
92
93        const fn is_sorted<const N: usize, V>(arr: &[(usize, V); N]) -> bool {
94            let mut i = 0;
95            while i < N - 1 {
96                if arr[i].0 >= arr[i + 1].0 {
97                    return false;
98                }
99                i += 1;
100            }
101            true
102        }
103
104        // SAFETY: The table is filled.
105        let table = unsafe { self.table.assume_init() };
106
107        debug_assert!(
108            is_sorted(&table),
109            "EnumTableBuilder: elements are not sorted by discriminant. Ensure that the elements are pushed in the correct order."
110        );
111
112        table
113    }
114
115    /// Builds the `EnumTable` from the pushed elements.
116    ///
117    /// # Returns
118    ///
119    /// An `EnumTable` containing the elements pushed into the builder.
120    pub const fn build_to(self) -> EnumTable<K, V, N> {
121        EnumTable::new(self.build())
122    }
123
124    /// Returns the number of elements the builder is expected to hold.
125    ///
126    /// # Returns
127    ///
128    /// The number of elements `N`.
129    pub const fn len(&self) -> usize {
130        N
131    }
132
133    /// Returns `true` if the builder has no elements pushed yet.
134    ///
135    /// # Returns
136    ///
137    /// `true` if no elements have been pushed, `false` otherwise.
138    pub const fn is_empty(&self) -> bool {
139        self.idx == 0
140    }
141}
142
143impl<K: Enumable, V, const N: usize> Default for EnumTableBuilder<K, V, N> {
144    fn default() -> Self {
145        Self::new()
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn builder() {
155        #[derive(Enumable)]
156        enum Test {
157            A,
158            B,
159            C,
160        }
161
162        const TABLE: EnumTable<Test, &'static str, { Test::COUNT }> = {
163            let mut builder = EnumTableBuilder::<Test, &'static str, { Test::COUNT }>::new();
164
165            let mut i = 0;
166            while i < builder.len() {
167                let t = &Test::VARIANTS[i];
168                builder.push(
169                    t,
170                    match t {
171                        Test::A => "A",
172                        Test::B => "B",
173                        Test::C => "C",
174                    },
175                );
176                i += 1;
177            }
178
179            builder.build_to()
180        };
181
182        assert_eq!(TABLE.get(&Test::A), &"A");
183        assert_eq!(TABLE.get(&Test::B), &"B");
184        assert_eq!(TABLE.get(&Test::C), &"C");
185    }
186}