premium_line/
lib.rs

1#![no_std]
2#![warn(missing_docs)]
3
4//! `Line` -- A fixed capacity `String`
5//!
6//! Since a `Line` has a fixed capacity it can be allocated on the stack or in static memory.
7
8use core::{borrow, cmp, error, fmt, hash, mem::MaybeUninit, ops, ptr, slice, str::FromStr};
9
10/// A `Line` is like a `String` with a fixed capacity
11///
12/// The capacity is not allowed to be larger than 255.
13pub struct Line<const CAPACITY: usize> {
14    content: [MaybeUninit<u8>; CAPACITY],
15    len: u8,
16}
17
18/// A `Line` with a capacity of 15 bytes
19pub type Line15 = Line<15>;
20/// A `Line` with a capacity of 31 bytes
21pub type Line31 = Line<31>;
22/// A `Line` with a capacity of 63 bytes
23pub type Line63 = Line<63>;
24/// A `Line` with a capacity of 127 bytes
25pub type Line127 = Line<127>;
26/// A `Line` with a capacity of 255 bytes
27pub type Line255 = Line<255>;
28
29/// Error type for mutating `Line`s
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum Error {
32    /// The `Line` does not have enough capacity
33    NoSpace,
34}
35
36impl<const N: usize> Line<N> {
37    /// Create a new Line.
38    pub const fn new() -> Self {
39        assert!(N < 256);
40        Self {
41            content: [MaybeUninit::uninit(); N],
42            len: 0,
43        }
44    }
45    /// Clear the line and set its length to 0.
46    pub fn clear(&mut self) {
47        self.len = 0;
48    }
49    /// Returns the `Line`'s capacity in bytes.
50    pub const fn capacity(&self) -> usize {
51        N
52    }
53    /// Extracts a string slice containing the entire `Line`.
54    pub fn as_str(&self) -> &str {
55        // Safety: We know the buffer is valid utf-8 up to len.
56        unsafe {
57            core::str::from_utf8_unchecked(slice::from_raw_parts(
58                self.content.as_ptr().cast(),
59                self.len as usize,
60            ))
61        }
62    }
63    /// Converts a `Line` into a mutable string slice.
64    pub fn as_mut_str(&mut self) -> &mut str {
65        // Safety: We know the buffer is valid utf-8 up to len.
66        unsafe {
67            core::str::from_utf8_unchecked_mut(slice::from_raw_parts_mut(
68                self.content.as_mut_ptr().cast(),
69                self.len as usize,
70            ))
71        }
72    }
73    /// Append the text to the `Line`.
74    pub fn push_str(&mut self, s: &str) -> Result<(), Error> {
75        assert!(N < 256);
76        let l = self.len as usize;
77        if l + s.len() <= N {
78            // Safety: We know there is enough space and the memory does not overlap.
79            unsafe {
80                ptr::copy_nonoverlapping(
81                    s.as_ptr(),
82                    self.content[l..l + s.len()].as_mut_ptr().cast(),
83                    s.len(),
84                );
85            }
86            self.len += s.len() as u8;
87            Ok(())
88        } else {
89            Err(Error::NoSpace)
90        }
91    }
92}
93impl<const N: usize> Clone for Line<N> {
94    fn clone(&self) -> Self {
95        let mut content = [MaybeUninit::uninit(); N];
96        // Safety: self.content is properly initialized to self.len.
97        unsafe {
98            ptr::copy_nonoverlapping(
99                self.content.as_ptr(),
100                content.as_mut_ptr(),
101                self.len as usize,
102            );
103        }
104        Self {
105            content,
106            len: self.len,
107        }
108    }
109}
110impl<const N: usize> FromStr for Line<N> {
111    type Err = Error;
112    fn from_str(s: &str) -> Result<Self, Error> {
113        let mut line = Self::new();
114        line.push_str(s)?;
115        Ok(line)
116    }
117}
118impl<const N: usize> ops::Deref for Line<N> {
119    type Target = str;
120    fn deref(&self) -> &Self::Target {
121        self.as_str()
122    }
123}
124impl<const N: usize> AsRef<str> for Line<N> {
125    fn as_ref(&self) -> &str {
126        self.as_str()
127    }
128}
129impl<const N: usize> AsMut<str> for Line<N> {
130    fn as_mut(&mut self) -> &mut str {
131        self.as_mut_str()
132    }
133}
134impl<const N: usize> borrow::Borrow<str> for Line<N> {
135    fn borrow(&self) -> &str {
136        self.as_str()
137    }
138}
139impl<const N: usize> borrow::BorrowMut<str> for Line<N> {
140    fn borrow_mut(&mut self) -> &mut str {
141        self.as_mut_str()
142    }
143}
144impl<const N: usize> TryFrom<&str> for Line<N> {
145    type Error = Error;
146    fn try_from(value: &str) -> Result<Self, Self::Error> {
147        Self::from_str(value)
148    }
149}
150impl<const N: usize> fmt::Write for Line<N> {
151    fn write_str(&mut self, s: &str) -> fmt::Result {
152        self.push_str(s).map_err(|_| fmt::Error)
153    }
154}
155impl<const N: usize> fmt::Debug for Line<N> {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        self.as_str().fmt(f)
158    }
159}
160impl<const N: usize> fmt::Display for Line<N> {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        self.as_str().fmt(f)
163    }
164}
165impl<const N: usize> Default for Line<N> {
166    fn default() -> Self {
167        Self::new()
168    }
169}
170impl<const N: usize, const M: usize> PartialEq<Line<M>> for Line<N> {
171    fn eq(&self, other: &Line<M>) -> bool {
172        self.as_str() == other.as_str()
173    }
174}
175impl<const N: usize> Eq for Line<N> {}
176impl<const N: usize> PartialEq<str> for Line<N> {
177    fn eq(&self, other: &str) -> bool {
178        self.as_str() == other
179    }
180}
181impl<const N: usize> PartialEq<Line<N>> for str {
182    fn eq(&self, other: &Line<N>) -> bool {
183        self == other.as_str()
184    }
185}
186impl<'a, const N: usize> PartialEq<&'a str> for Line<N> {
187    fn eq(&self, other: &&'a str) -> bool {
188        self.as_str() == *other
189    }
190}
191impl<const N: usize> PartialEq<Line<N>> for &str {
192    fn eq(&self, other: &Line<N>) -> bool {
193        *self == other.as_str()
194    }
195}
196impl<const N: usize, const M: usize> PartialOrd<Line<M>> for Line<N> {
197    fn partial_cmp(&self, other: &Line<M>) -> Option<cmp::Ordering> {
198        Some(self.as_str().cmp(other.as_str()))
199    }
200}
201impl<const N: usize> Ord for Line<N> {
202    fn cmp(&self, other: &Self) -> cmp::Ordering {
203        self.as_str().cmp(other.as_str())
204    }
205}
206impl<const N: usize> PartialOrd<str> for Line<N> {
207    fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
208        Some(self.as_str().cmp(other))
209    }
210}
211impl<const N: usize> PartialOrd<Line<N>> for str {
212    fn partial_cmp(&self, other: &Line<N>) -> Option<cmp::Ordering> {
213        Some(self.cmp(other.as_str()))
214    }
215}
216impl<'a, const N: usize> PartialOrd<&'a str> for Line<N> {
217    fn partial_cmp(&self, other: &&'a str) -> Option<cmp::Ordering> {
218        Some(self.as_str().cmp(other))
219    }
220}
221impl<const N: usize> PartialOrd<Line<N>> for &str {
222    fn partial_cmp(&self, other: &Line<N>) -> Option<cmp::Ordering> {
223        Some(self.cmp(&other.as_str()))
224    }
225}
226impl<const N: usize> hash::Hash for Line<N> {
227    fn hash<H: hash::Hasher>(&self, state: &mut H) {
228        self.as_str().hash(state)
229    }
230}
231
232impl fmt::Display for Error {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        match self {
235            Self::NoSpace => f.write_str("not enough capacity"),
236        }
237    }
238}
239impl error::Error for Error {}
240
241#[cfg(test)]
242mod tests {
243    use core::fmt::Write;
244
245    use super::*;
246
247    #[test]
248    fn basics() {
249        let mut line = Line15::new();
250        write!(line, "{}", 1.5).unwrap();
251        assert_eq!("1.5", line);
252        assert!("1.6" > line);
253        let mut line2 = Line255::new();
254        write!(line2, "{line:?}").unwrap();
255        assert_eq!(line2, "\"1.5\"");
256        assert_eq!(line.clone(), line);
257        let line3 = Line31::from_str(&line2).unwrap();
258        assert_eq!(line2, line3);
259        assert!(Line15::from_str("b").unwrap() > Line63::from_str("a").unwrap());
260        assert_eq!(Line15::from_str("a").unwrap().as_bytes(), &[b'a']);
261    }
262
263    #[test]
264    fn maps() {
265        extern crate std;
266        use std::collections::{BTreeMap, HashMap};
267        let mut m = HashMap::new();
268        m.insert(Line15::from_str("test").unwrap(), 1234);
269        m.insert("elite".try_into().unwrap(), 1337);
270        assert_eq!(m.get("test"), Some(&1234));
271        let mut m = BTreeMap::new();
272        m.insert(Line15::from_str("test").unwrap(), 1234);
273        assert_eq!(m.get("test"), Some(&1234));
274    }
275
276    #[test]
277    #[should_panic]
278    fn invalid_capacity() {
279        let _: Line<256> = Line::new();
280    }
281}