Skip to main content

ab_io_type/
fixed_capacity_bytes.rs

1#[cfg(test)]
2mod tests;
3
4use crate::metadata::{IoTypeMetadataKind, MAX_METADATA_CAPACITY, concat_metadata_sources};
5use crate::trivial_type::TrivialType;
6
7/// Ensures supported capacity of [`FixedCapacityBytesU8`] and [`FixedCapacityStringU8`]
8///
9/// [`FixedCapacityStringU8`]: crate::fixed_capacity_string::FixedCapacityStringU8
10pub(crate) const SUPPORTED_CAPACITY_U8<const CAPACITY: usize>: usize = {
11    assert!(
12        CAPACITY <= u8::MAX as usize,
13        "Capacity must not exceed `u8::MAX`"
14    );
15    0
16};
17/// Ensures supported capacity of [`FixedCapacityBytesU16`] and [`FixedCapacityStringU16`]
18///
19/// [`FixedCapacityStringU16`]: crate::fixed_capacity_string::FixedCapacityStringU16
20pub(crate) const SUPPORTED_CAPACITY_U16<const CAPACITY: usize>: usize = {
21    assert!(
22        CAPACITY <= u16::MAX as usize,
23        "Capacity must not exceed `u16::MAX`"
24    );
25    // Odd capacity results in a padding byte after the contents, which `TrivialType` doesn't allow
26    assert!(
27        CAPACITY.is_multiple_of(2),
28        "Capacity must be a multiple of 2"
29    );
30    0
31};
32
33/// Container for storing a number of bytes limited by the specified fixed capacity as `u8`.
34///
35/// See also [`FixedCapacityBytesU16`] if you need to store more bytes.
36///
37/// `CAPACITY` must not exceed `u8::MAX`.
38///
39/// In contrast to [`VariableBytes`], which can store arbitrary amount of data and can change the
40/// capacity, this container has fixed predefined capacity and occupies it regardless of how many
41/// bytes are actually stored inside. This might seem limiting but allows implementing
42/// [`TrivialType`] trait, enabling its use for fields in data structures that derive
43/// [`TrivialType`] themselves, which isn't the case with [`VariableBytes`].
44///
45/// [`VariableBytes`]: crate::variable_bytes::VariableBytes
46#[derive(Debug, Copy, Clone)]
47#[repr(C)]
48pub struct FixedCapacityBytesU8<const CAPACITY: usize> {
49    len: u8,
50    bytes: [u8; CAPACITY],
51    // TODO: Technically redundant with the where-clause, which is not enforced in generic code
52    //  due to https://github.com/rust-lang/rust/issues/164019, remove once fixed. Every instance
53    //  evaluates the check through this field. It is initialized in `const` blocks, which are
54    //  evaluated even if optimizations remove the field.
55    _supported: [(); SUPPORTED_CAPACITY_U8::<CAPACITY>],
56}
57
58impl<const CAPACITY: usize> Default for FixedCapacityBytesU8<CAPACITY>
59where
60    [(); SUPPORTED_CAPACITY_U8::<CAPACITY>]:,
61{
62    #[inline(always)]
63    fn default() -> Self {
64        Self {
65            len: 0,
66            bytes: [0; CAPACITY],
67            _supported: const { [(); SUPPORTED_CAPACITY_U8::<CAPACITY>] },
68        }
69    }
70}
71
72// SAFETY: Any bit pattern is valid, so it is safe to implement `TrivialType` for this type
73unsafe impl<const CAPACITY: usize> TrivialType for FixedCapacityBytesU8<CAPACITY>
74where
75    [(); SUPPORTED_CAPACITY_U8::<CAPACITY>]:,
76{
77    const METADATA: &[u8] = {
78        #[inline(always)]
79        const fn metadata(capacity: usize) -> ([u8; MAX_METADATA_CAPACITY], usize) {
80            concat_metadata_sources(&[&[
81                IoTypeMetadataKind::FixedCapacityBytes8b as u8,
82                capacity as u8,
83            ]])
84        }
85        metadata(CAPACITY).0.split_at(metadata(CAPACITY).1).0
86    };
87}
88
89impl<const CAPACITY: usize> FixedCapacityBytesU8<CAPACITY>
90where
91    [(); SUPPORTED_CAPACITY_U8::<CAPACITY>]:,
92{
93    /// Try to create an instance from provided bytes.
94    ///
95    /// Returns `None` if provided bytes do not fit into the capacity.
96    #[inline(always)]
97    pub fn try_from_bytes(bytes: &[u8]) -> Option<Self> {
98        if bytes.len() > CAPACITY {
99            return None;
100        }
101
102        Some(Self {
103            len: bytes.len() as u8,
104            bytes: {
105                let mut buffer = [0; CAPACITY];
106                buffer[..bytes.len()].copy_from_slice(bytes);
107                buffer
108            },
109            _supported: const { [(); SUPPORTED_CAPACITY_U8::<CAPACITY>] },
110        })
111    }
112
113    /// Access to stored bytes
114    #[inline(always)]
115    pub fn get_bytes(&self) -> &[u8] {
116        &self.bytes[..self.len as usize]
117    }
118
119    /// Exclusive access to stored bytes
120    #[inline(always)]
121    pub fn get_bytes_mut(&mut self) -> &mut [u8] {
122        &mut self.bytes[..self.len as usize]
123    }
124
125    /// Number of stored bytes
126    #[inline(always)]
127    pub const fn len(&self) -> u8 {
128        self.len
129    }
130
131    /// Returns `true` if length is zero
132    #[inline(always)]
133    pub const fn is_empty(&self) -> bool {
134        self.len == 0
135    }
136
137    /// Append some bytes.
138    ///
139    /// `true` is returned on success, but if there isn't enough capacity left, `false` is.
140    #[inline(always)]
141    #[must_use = "Operation may fail"]
142    pub fn append(&mut self, bytes: &[u8]) -> bool {
143        let len = self.len();
144        if bytes.len() + len as usize > CAPACITY {
145            return false;
146        }
147
148        self.bytes[len as usize..][..bytes.len()].copy_from_slice(bytes);
149        self.len += bytes.len() as u8;
150
151        true
152    }
153
154    /// Truncate stored bytes to this length.
155    ///
156    /// Returns `true` on success or `false` if `new_len` is larger than [`Self::len()`].
157    #[inline(always)]
158    #[must_use = "Operation may fail"]
159    pub fn truncate(&mut self, new_len: u8) -> bool {
160        if new_len > self.len() {
161            return false;
162        }
163
164        self.len = new_len;
165
166        true
167    }
168
169    /// Copy from specified bytes.
170    ///
171    /// Returns `false` if capacity is not enough to copy contents of `src`
172    #[inline(always)]
173    #[must_use = "Operation may fail"]
174    pub fn copy_from<T>(&mut self, src: &[u8]) -> bool {
175        if src.len() > CAPACITY {
176            return false;
177        }
178
179        self.bytes[..src.len()].copy_from_slice(src);
180        self.len = src.len() as u8;
181
182        true
183    }
184}
185
186/// Container for storing a number of bytes limited by the specified fixed capacity as `u16`.
187///
188/// See also [`FixedCapacityBytesU8`] if you need to store fewer bytes.
189///
190/// `CAPACITY` must not exceed `u16::MAX` and must be a multiple of 2 to avoid padding.
191///
192/// In contrast to [`VariableBytes`], which can store arbitrary amount of data and can change the
193/// capacity, this container has fixed predefined capacity and occupies it regardless of how many
194/// bytes are actually stored inside. This might seem limiting but allows implementing
195/// [`TrivialType`] trait, enabling its use for fields in data structures that derive
196/// [`TrivialType`] themselves, which isn't the case with [`VariableBytes`].
197///
198/// [`VariableBytes`]: crate::variable_bytes::VariableBytes
199#[derive(Debug, Copy, Clone)]
200#[repr(C)]
201pub struct FixedCapacityBytesU16<const CAPACITY: usize> {
202    len: u16,
203    bytes: [u8; CAPACITY],
204    // TODO: Technically redundant with the where-clause, which is not enforced in generic code
205    //  due to https://github.com/rust-lang/rust/issues/164019, remove once fixed. Every instance
206    //  evaluates the check through this field. It is initialized in `const` blocks, which are
207    //  evaluated even if optimizations remove the field.
208    _supported: [(); SUPPORTED_CAPACITY_U16::<CAPACITY>],
209}
210
211impl<const CAPACITY: usize> Default for FixedCapacityBytesU16<CAPACITY>
212where
213    [(); SUPPORTED_CAPACITY_U16::<CAPACITY>]:,
214{
215    #[inline(always)]
216    fn default() -> Self {
217        Self {
218            len: 0,
219            bytes: [0; CAPACITY],
220            _supported: const { [(); SUPPORTED_CAPACITY_U16::<CAPACITY>] },
221        }
222    }
223}
224
225// SAFETY: Any bit pattern is valid and there is no padding since capacity is a multiple of 2, so it
226// is safe to implement `TrivialType` for this type
227unsafe impl<const CAPACITY: usize> TrivialType for FixedCapacityBytesU16<CAPACITY>
228where
229    [(); SUPPORTED_CAPACITY_U16::<CAPACITY>]:,
230{
231    const METADATA: &[u8] = {
232        #[inline(always)]
233        const fn metadata(capacity: usize) -> ([u8; MAX_METADATA_CAPACITY], usize) {
234            concat_metadata_sources(&[
235                &[IoTypeMetadataKind::FixedCapacityBytes16b as u8],
236                &(capacity as u16).to_le_bytes(),
237            ])
238        }
239        metadata(CAPACITY).0.split_at(metadata(CAPACITY).1).0
240    };
241}
242
243impl<const CAPACITY: usize> FixedCapacityBytesU16<CAPACITY>
244where
245    [(); SUPPORTED_CAPACITY_U16::<CAPACITY>]:,
246{
247    /// Try to create an instance from provided bytes.
248    ///
249    /// Returns `None` if provided bytes do not fit into the capacity.
250    #[inline(always)]
251    pub fn try_from_bytes(bytes: &[u8]) -> Option<Self> {
252        if bytes.len() > CAPACITY {
253            return None;
254        }
255
256        Some(Self {
257            len: bytes.len() as u16,
258            bytes: {
259                let mut buffer = [0; CAPACITY];
260                buffer[..bytes.len()].copy_from_slice(bytes);
261                buffer
262            },
263            _supported: const { [(); SUPPORTED_CAPACITY_U16::<CAPACITY>] },
264        })
265    }
266
267    /// Access to stored bytes
268    #[inline(always)]
269    pub fn get_bytes(&self) -> &[u8] {
270        &self.bytes[..self.len as usize]
271    }
272
273    /// Exclusive access to stored bytes
274    #[inline(always)]
275    pub fn get_bytes_mut(&mut self) -> &mut [u8] {
276        &mut self.bytes[..self.len as usize]
277    }
278
279    /// Number of stored bytes
280    #[inline(always)]
281    pub const fn len(&self) -> u16 {
282        self.len
283    }
284
285    /// Returns `true` if length is zero
286    #[inline(always)]
287    pub const fn is_empty(&self) -> bool {
288        self.len == 0
289    }
290
291    /// Append some bytes.
292    ///
293    /// `true` is returned on success, but if there isn't enough capacity left, `false` is.
294    #[inline(always)]
295    #[must_use = "Operation may fail"]
296    pub fn append(&mut self, bytes: &[u8]) -> bool {
297        let len = self.len();
298        if bytes.len() + len as usize > CAPACITY {
299            return false;
300        }
301
302        self.bytes[len as usize..][..bytes.len()].copy_from_slice(bytes);
303        self.len += bytes.len() as u16;
304
305        true
306    }
307
308    /// Truncate stored bytes to this length.
309    ///
310    /// Returns `true` on success or `false` if `new_len` is larger than [`Self::len()`].
311    #[inline(always)]
312    #[must_use = "Operation may fail"]
313    pub fn truncate(&mut self, new_len: u16) -> bool {
314        if new_len > self.len() {
315            return false;
316        }
317
318        self.len = new_len;
319
320        true
321    }
322
323    /// Copy from specified bytes.
324    ///
325    /// Returns `false` if capacity is not enough to copy contents of `src`
326    #[inline(always)]
327    #[must_use = "Operation may fail"]
328    pub fn copy_from<T>(&mut self, src: &[u8]) -> bool {
329        if src.len() > CAPACITY {
330            return false;
331        }
332
333        self.bytes[..src.len()].copy_from_slice(src);
334        self.len = src.len() as u16;
335
336        true
337    }
338}