Skip to main content

kinavis_kernel/
inline.rs

1//! Fixed-capacity storage; nothing built on the kernel needs an allocator.
2//!
3//! Bridge navigation computers are often heapless microcontrollers, and
4//! everything the navigation crates store has a natural bound (swing nodes
5//! every few degrees, a few dozen waypoints, a short error excerpt): fixed
6//! capacity, checked on insertion.
7//!
8//! Public so that aggregates in `kinavis` (deviation table, route) and adapters
9//! with the same constraint share it.
10//!
11//! No `unsafe`: [`Inline`] holds a fully initialised array and a live length;
12//! the unused tail is never visible through [`Inline::as_slice`].
13
14use core::fmt;
15use core::ops::Deref;
16
17/// Vector of at most `N` items, stored inline.
18///
19/// Capacity is part of the type; `push` and `insert` report a full store
20/// instead of growing or truncating. Items are read through the slice it
21/// dereferences to.
22///
23/// `T: Copy` because the array is pre-filled with a fill value, which avoids
24/// `unsafe`.
25#[derive(Clone, Copy)]
26pub struct Inline<T: Copy, const N: usize> {
27    items: [T; N],
28    len: usize,
29}
30
31impl<T: Copy, const N: usize> Inline<T, N> {
32    /// Empty store; `fill` only occupies the unused tail.
33    #[must_use]
34    pub const fn new(fill: T) -> Self {
35        Self {
36            items: [fill; N],
37            len: 0,
38        }
39    }
40
41    /// Appends an item.
42    ///
43    /// # Errors
44    ///
45    /// [`Full`] if `N` items are held; the store is unchanged.
46    pub fn push(&mut self, item: T) -> Result<(), Full> {
47        // Read the length once and compare before storing, so the compiler sees
48        // the increment cannot wrap; re-reading after the store would lose that
49        // proof (possible aliasing).
50        let len = self.len;
51        if len >= N {
52            return Err(Full { capacity: N });
53        }
54        let slot = self.items.get_mut(len).ok_or(Full { capacity: N })?;
55        *slot = item;
56        self.len = len + 1;
57        Ok(())
58    }
59
60    /// Inserts at `index`, shifting later items; an index past the end appends.
61    ///
62    /// # Errors
63    ///
64    /// [`Full`] if `N` items are held; the store is unchanged.
65    pub fn insert(&mut self, index: usize, item: T) -> Result<(), Full> {
66        // Read once, as in `push`, so every step is visibly bounded by `N`.
67        let len = self.len;
68        if len >= N {
69            return Err(Full { capacity: N });
70        }
71        let index = index.min(len);
72        // Iterate backwards so nothing is overwritten.
73        let mut source = len;
74        while source > index {
75            source -= 1;
76            let value = *self.items.get(source).ok_or(Full { capacity: N })?;
77            *self.items.get_mut(source + 1).ok_or(Full { capacity: N })? = value;
78        }
79        *self.items.get_mut(index).ok_or(Full { capacity: N })? = item;
80        self.len = len + 1;
81        Ok(())
82    }
83
84    /// Removes and returns the item at `index`, closing the gap; `None` past
85    /// the end, store unchanged.
86    pub fn remove(&mut self, index: usize) -> Option<T> {
87        let len = self.len;
88        if index >= len {
89            return None;
90        }
91        let removed = *self.items.get(index)?;
92        // Shift the tail down; `index < len <= N` bounds every step.
93        let mut at = index;
94        while at + 1 < len {
95            let next = *self.items.get(at + 1)?;
96            *self.items.get_mut(at)? = next;
97            at += 1;
98        }
99        self.len = len - 1;
100        Some(removed)
101    }
102
103    /// Items.
104    #[must_use]
105    pub fn as_slice(&self) -> &[T] {
106        self.items.get(..self.len).unwrap_or(&[])
107    }
108
109    /// Items, mutable.
110    #[must_use]
111    pub fn as_mut_slice(&mut self) -> &mut [T] {
112        self.items.get_mut(..self.len).unwrap_or(&mut [])
113    }
114
115    /// Length.
116    #[must_use]
117    pub const fn len(&self) -> usize {
118        self.len
119    }
120
121    /// Whether empty.
122    #[must_use]
123    pub const fn is_empty(&self) -> bool {
124        self.len == 0
125    }
126
127    /// Capacity.
128    #[must_use]
129    pub const fn capacity() -> usize {
130        N
131    }
132}
133
134impl<T: Copy, const N: usize> Deref for Inline<T, N> {
135    type Target = [T];
136
137    fn deref(&self) -> &[T] {
138        self.as_slice()
139    }
140}
141
142impl<T: Copy + fmt::Debug, const N: usize> fmt::Debug for Inline<T, N> {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        f.debug_list().entries(self.as_slice()).finish()
145    }
146}
147
148impl<T: Copy + PartialEq, const N: usize> PartialEq for Inline<T, N> {
149    /// Compares live items only.
150    fn eq(&self, other: &Self) -> bool {
151        self.as_slice() == other.as_slice()
152    }
153}
154
155/// Store full.
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub struct Full {
158    /// Capacity.
159    pub capacity: usize,
160}
161
162impl fmt::Display for Full {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        write!(f, "no room left in a store of {} items", self.capacity)
165    }
166}
167
168impl core::error::Error for Full {}
169
170/// Short inline string.
171///
172/// Used for error excerpts. Input longer than `N` bytes is truncated on a
173/// character boundary with an ellipsis.
174#[derive(Clone, Copy, PartialEq, Eq)]
175pub struct InlineStr<const N: usize> {
176    bytes: [u8; N],
177    len: usize,
178}
179
180impl<const N: usize> InlineStr<N> {
181    /// Copies as much of `text` as fits, truncating on a character boundary.
182    #[must_use]
183    pub fn new(text: &str) -> Self {
184        let mut bytes = [0_u8; N];
185        let mut len = 0;
186        for (index, character) in text.char_indices() {
187            // A `str` byte index plus at most four cannot wrap, but the
188            // compiler does not know `str` length is bounded.
189            let end = index.saturating_add(character.len_utf8());
190            if end > N {
191                break;
192            }
193            len = end;
194        }
195        for (slot, byte) in bytes.iter_mut().zip(text.as_bytes().iter().take(len)) {
196            *slot = *byte;
197        }
198        Self { bytes, len }
199    }
200
201    /// Text, possibly truncated.
202    #[must_use]
203    pub fn as_str(&self) -> &str {
204        self.bytes
205            .get(..self.len)
206            .and_then(|bytes| core::str::from_utf8(bytes).ok())
207            .unwrap_or("")
208    }
209
210    /// Capacity in bytes.
211    #[must_use]
212    pub const fn capacity() -> usize {
213        N
214    }
215}
216
217impl<const N: usize> fmt::Display for InlineStr<N> {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        f.write_str(self.as_str())
220    }
221}
222
223impl<const N: usize> fmt::Debug for InlineStr<N> {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        fmt::Debug::fmt(self.as_str(), f)
226    }
227}
228
229impl<const N: usize> PartialEq<str> for InlineStr<N> {
230    fn eq(&self, other: &str) -> bool {
231        self.as_str() == other
232    }
233}
234
235impl<const N: usize> PartialEq<&str> for InlineStr<N> {
236    fn eq(&self, other: &&str) -> bool {
237        self.as_str() == *other
238    }
239}
240
241impl<const N: usize> From<&str> for InlineStr<N> {
242    fn from(text: &str) -> Self {
243        Self::new(text)
244    }
245}
246
247#[cfg(feature = "serde")]
248impl<const N: usize> serde::Serialize for InlineStr<N> {
249    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
250        serializer.serialize_str(self.as_str())
251    }
252}
253
254#[cfg(feature = "serde")]
255impl<'de, const N: usize> serde::Deserialize<'de> for InlineStr<N> {
256    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
257        struct Visitor<const N: usize>;
258
259        impl<const N: usize> serde::de::Visitor<'_> for Visitor<N> {
260            type Value = InlineStr<N>;
261
262            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263                write!(f, "a string of at most {N} bytes")
264            }
265
266            fn visit_str<E: serde::de::Error>(self, text: &str) -> Result<Self::Value, E> {
267                Ok(InlineStr::new(text))
268            }
269        }
270
271        deserializer.deserialize_str(Visitor::<N>)
272    }
273}
274
275#[cfg(test)]
276#[allow(clippy::unwrap_used)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn an_inline_store_fills_and_then_refuses() {
282        let mut store = Inline::<u8, 3>::new(0);
283        assert!(store.is_empty());
284        for value in 1..=3 {
285            store.push(value).unwrap();
286        }
287        assert_eq!(store.as_slice(), &[1, 2, 3]);
288        assert_eq!(store.push(4), Err(Full { capacity: 3 }));
289        assert_eq!(store.as_slice(), &[1, 2, 3]);
290    }
291
292    #[test]
293    fn insert_shifts_the_tail_along() {
294        let mut store = Inline::<u8, 4>::new(0);
295        store.push(1).unwrap();
296        store.push(3).unwrap();
297        store.insert(1, 2).unwrap();
298        assert_eq!(store.as_slice(), &[1, 2, 3]);
299        // An index past the end appends.
300        store.insert(99, 4).unwrap();
301        assert_eq!(store.as_slice(), &[1, 2, 3, 4]);
302        assert!(store.insert(0, 5).is_err());
303    }
304
305    #[test]
306    fn two_stores_are_equal_when_their_live_items_are() {
307        let mut first = Inline::<u8, 8>::new(0);
308        let mut second = Inline::<u8, 8>::new(9);
309        first.push(1).unwrap();
310        second.push(1).unwrap();
311        assert_eq!(first, second);
312    }
313
314    #[test]
315    fn an_inline_string_truncates_on_a_character_boundary() {
316        let short = InlineStr::<8>::new("north");
317        assert_eq!(short.as_str(), "north");
318        assert_eq!(short, "north");
319
320        // Four bytes of capacity; each character takes two.
321        let long = InlineStr::<4>::new("°°°");
322        assert_eq!(long.as_str(), "°°");
323
324        // A character that does not fit yields an empty string, never invalid
325        // UTF-8.
326        assert_eq!(InlineStr::<1>::new("°").as_str(), "");
327    }
328}