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 // SAFETY: The table is filled.
94 unsafe { self.table.assume_init() }
95 }
96
97 /// Builds the `EnumTable` from the pushed elements.
98 ///
99 /// # Returns
100 ///
101 /// An `EnumTable` containing the elements pushed into the builder.
102 pub const fn build_to(self) -> EnumTable<K, V, N> {
103 EnumTable::new(self.build())
104 }
105
106 /// Returns the number of elements the builder is expected to hold.
107 ///
108 /// # Returns
109 ///
110 /// The number of elements `N`.
111 pub const fn len(&self) -> usize {
112 N
113 }
114
115 /// Returns `true` if the builder has no elements pushed yet.
116 ///
117 /// # Returns
118 ///
119 /// `true` if no elements have been pushed, `false` otherwise.
120 pub const fn is_empty(&self) -> bool {
121 self.idx == 0
122 }
123}
124
125impl<K: Enumable, V, const N: usize> Default for EnumTableBuilder<K, V, N> {
126 fn default() -> Self {
127 Self::new()
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn builder() {
137 #[derive(Enumable)]
138 enum Test {
139 A,
140 B,
141 C,
142 }
143
144 const TABLE: EnumTable<Test, &'static str, { Test::COUNT }> = {
145 let mut builder = EnumTableBuilder::<Test, &'static str, { Test::COUNT }>::new();
146
147 let mut i = 0;
148 while i < builder.len() {
149 let t = &Test::VARIANTS[i];
150 builder.push(
151 t,
152 match t {
153 Test::A => "A",
154 Test::B => "B",
155 Test::C => "C",
156 },
157 );
158 i += 1;
159 }
160
161 builder.build_to()
162 };
163
164 assert_eq!(TABLE.get(&Test::A), &"A");
165 assert_eq!(TABLE.get(&Test::B), &"B");
166 assert_eq!(TABLE.get(&Test::C), &"C");
167 }
168}