ab_io_type/fixed_capacity_elements.rs
1#[cfg(test)]
2mod tests;
3
4use crate::alignment::Alignment;
5use crate::metadata::{IoTypeMetadataKind, MAX_METADATA_CAPACITY, concat_metadata_sources};
6use crate::trivial_type::{TrivialType, try_skip_valid};
7use crate::unaligned::Unaligned;
8use core::fmt;
9
10/// Size of the prefix of [`FixedCapacityElements`] that stores the number of elements.
11///
12/// The number of elements is stored as `u32`, the prefix is extended to
13/// [`TrivialType::ALIGNMENT`] of elements if it is larger, such that elements are aligned without
14/// padding before them.
15const LEN_SIZE<Element: TrivialType>: usize =
16 usize::from(Alignment::of::<Element>().get()).max(size_of::<u32>());
17/// Ensures the capacity (encoded in metadata) and the size of [`FixedCapacityElements`] and
18/// [`FixedCapacityString`] fit into `u32`
19///
20/// [`FixedCapacityString`]: crate::fixed_capacity_string::FixedCapacityString
21pub(crate) const SUPPORTED_CAPACITY<Element: TrivialType, const CAPACITY: usize>: usize = {
22 assert!(
23 u32::MAX as usize >= CAPACITY,
24 "Capacity must not exceed `u32::MAX`"
25 );
26 assert!(
27 u32::MAX as usize >= size_of::<[Element; CAPACITY]>()
28 && u32::MAX as usize - size_of::<[Element; CAPACITY]>() >= LEN_SIZE::<Element>,
29 "Type size must be smaller than 2^32"
30 );
31 0
32};
33
34/// Container for storing a number of elements limited by the specified fixed capacity.
35///
36/// See also [`FixedCapacityBytes`] and [`FixedCapacityString`].
37///
38/// In contrast to [`VariableElements`], which can store arbitrary number of elements and can
39/// change the capacity, this container has fixed predefined capacity and occupies it regardless of
40/// how many elements are actually stored inside. This might seem limiting but allows implementing
41/// [`TrivialType`] trait, enabling its use for fields in data structures that derive
42/// [`TrivialType`] themselves, which isn't the case with [`VariableElements`].
43///
44/// The length (the number of stored elements) is a little-endian `u32` in the first 4 bytes,
45/// followed by zero bytes up to [`TrivialType::ALIGNMENT`] of elements if it is larger than 4
46/// bytes, and `CAPACITY` elements after that. As a result, the container has the same alignment as
47/// elements and no padding, regardless of the alignment of elements.
48///
49/// `CAPACITY` must not exceed `u32::MAX`, and the size of the container must be smaller than
50/// 2^32.
51///
52/// Parsing rejects a length above `CAPACITY`, but doesn't check the rest of the length prefix,
53/// which is set to zero bytes whenever the length is set. The unsafe methods of [`TrivialType`]
54/// that skip parsing can create an instance with a larger length: methods don't panic then, but
55/// such an instance has no accessible elements and nothing can be appended to it until a valid
56/// length is set with [`Self::truncate()`] or [`Self::copy_from()`].
57///
58/// Elements beyond the length are not part of the value. [`Default`] and [`Self::try_from_slice()`]
59/// fill them with default elements, while [`Self::truncate()`] and [`Self::copy_from()`] leave
60/// them as they are, so bytes of instances with the same elements can differ. Parsing checks all
61/// `CAPACITY` elements, they are values of `Element` too.
62///
63/// [`FixedCapacityBytes`]: crate::fixed_capacity_bytes::FixedCapacityBytes
64/// [`FixedCapacityString`]: crate::fixed_capacity_string::FixedCapacityString
65/// [`VariableElements`]: crate::variable_elements::VariableElements
66#[derive(Copy, Clone)]
67#[repr(C)]
68pub struct FixedCapacityElements<Element, const CAPACITY: usize>
69where
70 Element: TrivialType,
71{
72 len: [u8; LEN_SIZE::<Element>],
73 elements: [Element; CAPACITY],
74 // TODO: Technically redundant with the where-clause, which is not enforced in generic code
75 // due to https://github.com/rust-lang/rust/issues/164019, remove once fixed. Every instance
76 // evaluates the check through this field. It is initialized in `const` blocks, which are
77 // evaluated even if optimizations remove the field.
78 _supported: [(); SUPPORTED_CAPACITY::<Element, CAPACITY>],
79}
80
81impl<Element, const CAPACITY: usize> fmt::Debug for FixedCapacityElements<Element, CAPACITY>
82where
83 Element: TrivialType + fmt::Debug,
84 [(); SUPPORTED_CAPACITY::<Element, CAPACITY>]:,
85{
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 f.debug_struct("FixedCapacityElements")
88 .field("len", &self.len())
89 .field("elements", &self.get_elements())
90 .finish()
91 }
92}
93
94impl<Element, const CAPACITY: usize> Default for FixedCapacityElements<Element, CAPACITY>
95where
96 Element: TrivialType + Default,
97 [(); SUPPORTED_CAPACITY::<Element, CAPACITY>]:,
98{
99 #[inline(always)]
100 fn default() -> Self {
101 Self {
102 len: [0; LEN_SIZE::<Element>],
103 elements: [Element::default(); CAPACITY],
104 _supported: const { [(); SUPPORTED_CAPACITY::<Element, CAPACITY>] },
105 }
106 }
107}
108
109// SAFETY: The length prefix consists of bytes and elements are `TrivialType`. There is no padding
110// before elements: the size of the length prefix is a multiple of `Element::ALIGNMENT`, and so of
111// the alignment of elements, which is a smaller or equal power of two (`Alignment::of()` checks
112// it). There is no padding after elements either: their size is a multiple of their alignment,
113// which is the alignment of the container. `ALIGNMENT` is that of elements, like in metadata. The
114// parsing step checks the length and every element.
115unsafe impl<Element, const CAPACITY: usize> TrivialType for FixedCapacityElements<Element, CAPACITY>
116where
117 Element: TrivialType,
118 [(); SUPPORTED_CAPACITY::<Element, CAPACITY>]:,
119{
120 const ALIGNMENT: Alignment = Element::ALIGNMENT;
121 // Casting `CAPACITY` to `u32` is lossless, which is checked by the `SUPPORTED_CAPACITY` bound
122 const METADATA: &[u8] = {
123 #[inline(always)]
124 const fn metadata(
125 capacity: u32,
126 inner_metadata: &[u8],
127 ) -> ([u8; MAX_METADATA_CAPACITY], usize) {
128 concat_metadata_sources(&[
129 &[IoTypeMetadataKind::FixedCapacityElements as u8],
130 &capacity.to_le_bytes(),
131 inner_metadata,
132 ])
133 }
134
135 // The reference extends the lifetime of metadata scratch to that of the constant
136 let (metadata_scratch, metadata_size) = &metadata(CAPACITY as u32, Element::METADATA);
137 metadata_scratch.split_at(*metadata_size).0
138 };
139
140 #[inline(always)]
141 #[expect(
142 clippy::cast_ptr_alignment,
143 reason = "The length is only read as `u32` when `bytes` are aligned for it"
144 )]
145 unsafe fn parse_step<const ALIGNED: bool>(bytes: &[u8]) -> Option<(&Unaligned<Self>, &[u8])> {
146 let (bytes, remainder) = bytes.split_at_checked(size_of::<Self>())?;
147 let len = if ALIGNED && align_of::<Self>() >= align_of::<u32>() {
148 // SAFETY: `bytes` are aligned for `Self` (function contract), whose alignment is at
149 // least that of `u32` as checked above, and start with the length prefix of at least
150 // 4 bytes
151 u32::from_le(unsafe { bytes.as_ptr().cast::<u32>().read() })
152 } else {
153 u32::from_le_bytes(*bytes.first_chunk()?)
154 };
155 // Casting `CAPACITY` to `u32` is lossless, which is checked by the `SUPPORTED_CAPACITY`
156 // bound
157 if len > CAPACITY as u32 {
158 return None;
159 }
160 // SAFETY: If `ALIGNED`, `bytes` are aligned for `Self` (function contract), which has the
161 // alignment of elements, and elements start after the length prefix, whose size is a
162 // multiple of their alignment (see above)
163 unsafe { try_skip_valid::<Element, ALIGNED>(bytes.get(LEN_SIZE::<Element>..)?, CAPACITY) }?;
164 // SAFETY: The length prefix consists of bytes, the length doesn't exceed `CAPACITY` and
165 // every element is a valid value as checked above, they follow each other without padding
166 // (see above), `Unaligned<Self>` has alignment 1 and the size of `Self`
167 let value = unsafe { bytes.as_ptr().cast::<Unaligned<Self>>().as_ref_unchecked() };
168 Some((value, remainder))
169 }
170}
171
172impl<Element, const CAPACITY: usize> FixedCapacityElements<Element, CAPACITY>
173where
174 Element: TrivialType,
175 [(); SUPPORTED_CAPACITY::<Element, CAPACITY>]:,
176{
177 /// Try to create an instance from provided elements.
178 ///
179 /// Unused capacity is filled with default elements.
180 ///
181 /// Returns `None` if provided elements do not fit into the capacity.
182 #[inline(always)]
183 pub fn try_from_slice(elements: &[Element]) -> Option<Self>
184 where
185 Element: Default,
186 {
187 let mut instance = Self::default();
188 if !instance.copy_from(elements) {
189 return None;
190 }
191
192 Some(instance)
193 }
194
195 /// Access to stored elements.
196 ///
197 /// Returns no elements if the number of elements exceeds `CAPACITY`, which is only possible for
198 /// an instance created from invalid bytes.
199 #[inline(always)]
200 pub fn get_elements(&self) -> &[Element] {
201 self.elements.get(..self.len() as usize).unwrap_or_default()
202 }
203
204 /// Exclusive access to stored elements.
205 ///
206 /// Returns no elements if the number of elements exceeds `CAPACITY`, which is only possible for
207 /// an instance created from invalid bytes.
208 #[inline(always)]
209 pub fn get_elements_mut(&mut self) -> &mut [Element] {
210 let len = self.len() as usize;
211 self.elements.get_mut(..len).unwrap_or_default()
212 }
213
214 /// Number of stored elements.
215 ///
216 /// May exceed `CAPACITY` for an instance created from invalid bytes.
217 #[inline(always)]
218 pub const fn len(&self) -> u32 {
219 u32::from_le_bytes(
220 *self
221 .len
222 .first_chunk()
223 .expect("Length prefix is at least 4 bytes; qed"),
224 )
225 }
226
227 /// Returns `true` if there are no stored elements
228 #[inline(always)]
229 pub const fn is_empty(&self) -> bool {
230 self.len() == 0
231 }
232
233 /// Append some elements.
234 ///
235 /// `true` is returned on success, but if there isn't enough capacity left, `false` is.
236 #[inline(always)]
237 #[must_use = "Operation may fail"]
238 pub fn append(&mut self, elements: &[Element]) -> bool {
239 let len = self.len();
240 let Some(new_len) = u32::try_from(elements.len())
241 .ok()
242 .and_then(|count| len.checked_add(count))
243 else {
244 return false;
245 };
246 // `None` if the new length exceeds the capacity
247 let Some(target) = self.elements.get_mut(len as usize..new_len as usize) else {
248 return false;
249 };
250
251 target.copy_from_slice(elements);
252 self.set_len(new_len);
253
254 true
255 }
256
257 /// Truncate stored elements to this length.
258 ///
259 /// Returns `true` on success or `false` if `new_len` is larger than [`Self::len()`].
260 #[inline(always)]
261 #[must_use = "Operation may fail"]
262 pub const fn truncate(&mut self, new_len: u32) -> bool {
263 if new_len > self.len() {
264 return false;
265 }
266
267 self.set_len(new_len);
268
269 true
270 }
271
272 /// Copy from specified elements, replacing stored elements.
273 ///
274 /// Returns `false` if capacity is not enough to copy contents of `src`
275 #[inline(always)]
276 #[must_use = "Operation may fail"]
277 pub fn copy_from(&mut self, src: &[Element]) -> bool {
278 let Ok(new_len) = u32::try_from(src.len()) else {
279 return false;
280 };
281 let Some(target) = self.elements.get_mut(..src.len()) else {
282 return false;
283 };
284
285 target.copy_from_slice(src);
286 self.set_len(new_len);
287
288 true
289 }
290
291 /// Store the length in the first 4 bytes of the length prefix, the rest of it is zeroed
292 #[inline(always)]
293 const fn set_len(&mut self, len: u32) {
294 self.len = [0; LEN_SIZE::<Element>];
295 *self
296 .len
297 .first_chunk_mut()
298 .expect("Length prefix is at least 4 bytes; qed") = len.to_le_bytes();
299 }
300}