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