Skip to main content

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}