Skip to main content

orengine_utils/
small_string.rs

1//! A compact UTF-8 string backed by [`smallvec::SmallVec`].
2//!
3//! `SmallString` stores short strings inline without a heap allocation and
4//! transparently spills to the heap when the inline capacity is exceeded.
5//! It dereferences to `str`, making it convenient to use anywhere a string
6//! slice is expected.
7//!
8//! The type implements `serde::Serialize` and `serde::Deserialize` as a
9//! regular UTF-8 string.
10
11use alloc::string::String;
12#[cfg(not(feature = "no_std"))]
13use core::fmt;
14#[cfg(not(feature = "no_std"))]
15use core::fmt::Formatter;
16use core::ops::Deref;
17#[cfg(not(feature = "no_std"))]
18use serde::de::{Error, Visitor};
19#[cfg(not(feature = "no_std"))]
20use serde::{Deserialize, Deserializer, Serialize, Serializer};
21use smallvec::SmallVec;
22
23/// A UTF-8 string with configurable inline storage.
24///
25/// While the length is less or equal to `INLINE_SIZE`, the string is stored
26/// inline on a stack. When the length exceeds this limit, the string spills
27/// to the heap.
28#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Debug)]
29pub struct SmallString<const INLINE_SIZE: usize>(SmallVec<u8, INLINE_SIZE>);
30
31impl<const INLINE_SIZE: usize> SmallString<INLINE_SIZE> {
32    /// Creates an empty string.
33    #[must_use]
34    pub fn empty() -> Self {
35        Self(SmallVec::new())
36    }
37
38    /// Reads a string of the specified length from a reader.
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if the reader fails to read.
43    #[cfg(not(feature = "no_std"))]
44    pub(crate) fn fill_from_reader<R: std::io::Read>(
45        mut reader: R,
46        len: usize,
47    ) -> Result<Self, std::io::Error> {
48        let mut res = Self(SmallVec::new());
49
50        res.0.resize(len, 0);
51        reader.read_exact(&mut res.0)?;
52
53        Ok(res)
54    }
55
56    /// Appends the provided UTF-8 bytes to the string.
57    ///
58    /// # Panics
59    ///
60    /// Panics if the resulting byte sequence is not valid UTF-8.
61    ///
62    /// This method is intended to be used only with valid UTF-8 data.
63    pub fn extend_from_slice(&mut self, bytes: &[u8]) {
64        self.0.extend_from_slice(bytes);
65
66        // Preserve the invariant that the contents are always valid UTF-8.
67        debug_assert!(core::str::from_utf8(&self.0).is_ok());
68    }
69}
70
71impl<const INLINE_SIZE: usize> From<&str> for SmallString<INLINE_SIZE> {
72    fn from(value: &str) -> Self {
73        Self(value.as_bytes().into())
74    }
75}
76
77impl<const INLINE_SIZE: usize> From<String> for SmallString<INLINE_SIZE> {
78    fn from(value: String) -> Self {
79        Self(value.into_bytes().into())
80    }
81}
82
83impl<const INLINE_SIZE: usize> Deref for SmallString<INLINE_SIZE> {
84    type Target = str;
85
86    fn deref(&self) -> &Self::Target {
87        core::str::from_utf8(&self.0).unwrap()
88    }
89}
90
91#[cfg(not(feature = "no_std"))]
92impl<const INLINE_SIZE: usize> Serialize for SmallString<INLINE_SIZE> {
93    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
94    where
95        S: Serializer,
96    {
97        serializer.serialize_str(self)
98    }
99}
100
101#[cfg(not(feature = "no_std"))]
102impl<'de, const INLINE_SIZE: usize> Deserialize<'de> for SmallString<INLINE_SIZE> {
103    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
104    where
105        D: Deserializer<'de>,
106    {
107        struct SmallStringVisitor<const INLINE_SIZE: usize>;
108
109        impl<const INLINE_SIZE: usize> Visitor<'_> for SmallStringVisitor<INLINE_SIZE> {
110            type Value = SmallString<INLINE_SIZE>;
111
112            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
113                formatter.write_str("a UTF-8 string")
114            }
115
116            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
117            where
118                E: Error,
119            {
120                Ok(SmallString::from(value))
121            }
122        }
123
124        deserializer.deserialize_str(SmallStringVisitor::<INLINE_SIZE>)
125    }
126
127    fn deserialize_in_place<D>(deserializer: D, place: &mut Self) -> Result<(), D::Error>
128    where
129        D: Deserializer<'de>,
130    {
131        struct SmallStringVisitorRef<'a, const INLINE_SIZE: usize>(
132            &'a mut SmallString<INLINE_SIZE>,
133        );
134
135        impl<const INLINE_SIZE: usize> Visitor<'_> for SmallStringVisitorRef<'_, INLINE_SIZE> {
136            type Value = ();
137
138            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
139                formatter.write_str("a UTF-8 string")
140            }
141
142            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
143            where
144                E: Error,
145            {
146                self.0 .0.clear();
147                self.0 .0.extend_from_slice(value.as_bytes());
148
149                Ok(())
150            }
151        }
152
153        deserializer.deserialize_str(SmallStringVisitorRef(place))?;
154
155        Ok(())
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::SmallString;
162    use alloc::string::String;
163
164    #[test]
165    fn empty_string_is_empty() {
166        let value = SmallString::<16>::empty();
167
168        assert!(value.is_empty());
169    }
170
171    #[test]
172    fn extend_appends_bytes() {
173        let mut value = SmallString::<8>::from("hello");
174
175        value.extend_from_slice(b" world");
176
177        assert_eq!(&*value, "hello world");
178    }
179
180    #[test]
181    fn from_string_and_str_are_equal() {
182        let a = SmallString::<8>::from("example");
183        let b = SmallString::<8>::from(String::from("example"));
184
185        assert_eq!(a, b);
186    }
187
188    #[test]
189    #[cfg(not(feature = "no_std"))]
190    fn serde() {
191        use crate::rw_serde::RWDeserializer;
192        use crate::rw_serde::RWSerializer;
193        use serde::{Deserialize, Serialize};
194        use std::io::Cursor;
195
196        let value = SmallString::<8>::from("small string");
197        let mut ser = RWSerializer::new(Vec::new());
198
199        value.serialize(&mut ser).unwrap();
200
201        let buf = ser.into_inner();
202        let mut de = RWDeserializer::new(Cursor::new(buf));
203        let restored = SmallString::<8>::deserialize(&mut de).unwrap();
204
205        assert_eq!(value, restored);
206    }
207}