byteview/
strview.rs

1// Copyright (c) 2024-present, fjall-rs
2// This source code is licensed under both the Apache 2.0 and MIT License
3// (found in the LICENSE-* files in the repository)
4
5use crate::ByteView;
6use std::{ops::Deref, sync::Arc};
7
8/// An immutable, UTF-8–encoded string slice
9///
10/// Will be inlined (no pointer dereference or heap allocation)
11/// if it is 20 characters or shorter (on a 64-bit system).
12///
13/// A single heap allocation will be shared between multiple strings.
14/// Even substrings of that heap allocation can be cloned without additional heap allocation.
15///
16/// Uses [`ByteView`] internally, but derefs as [`&str`].
17#[repr(C)]
18#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
19pub struct StrView(ByteView);
20
21#[allow(clippy::non_send_fields_in_send_ty)]
22unsafe impl Send for StrView {}
23#[allow(clippy::non_send_fields_in_send_ty)]
24unsafe impl Sync for StrView {}
25
26impl std::fmt::Display for StrView {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        write!(f, "{}", &**self)
29    }
30}
31
32impl std::fmt::Debug for StrView {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        write!(f, "{:?}", &**self)
35    }
36}
37
38impl Deref for StrView {
39    type Target = str;
40
41    fn deref(&self) -> &Self::Target {
42        // SAFETY: Constructor takes a &str
43        unsafe { std::str::from_utf8_unchecked(&self.0) }
44    }
45}
46
47impl std::hash::Hash for StrView {
48    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
49        self.deref().hash(state);
50    }
51}
52
53impl StrView {
54    /// Creates a new string from an existing byte string.
55    ///
56    /// Will heap-allocate the string if it has at least length 13.
57    ///
58    /// # Panics
59    ///
60    /// Panics if the length does not fit in a u32 (4 GiB).
61    #[must_use]
62    pub fn new(s: &str) -> Self {
63        Self(ByteView::new(s.as_bytes()))
64    }
65
66    #[doc(hidden)]
67    #[must_use]
68    #[allow(clippy::missing_const_for_fn)]
69    pub unsafe fn from_raw(view: ByteView) -> Self {
70        Self(view)
71    }
72
73    /// Clones the contents of this string into an independently tracked string.
74    #[must_use]
75    pub fn to_detached(&self) -> Self {
76        Self::new(self)
77    }
78
79    /// Clones the given range of the existing string without heap allocation.
80    #[must_use]
81    pub fn slice(&self, range: impl std::ops::RangeBounds<usize>) -> Self {
82        Self(self.0.slice(range))
83    }
84
85    /// Returns `true` if the string is empty.
86    #[must_use]
87    pub fn is_empty(&self) -> bool {
88        self.0.is_empty()
89    }
90
91    /// Returns the amount of bytes in the string.
92    #[must_use]
93    pub fn len(&self) -> usize {
94        self.0.len()
95    }
96
97    /// Returns `true` if `needle` is a prefix of the string or equal to the string.
98    #[must_use]
99    pub fn starts_with(&self, needle: &str) -> bool {
100        self.0.starts_with(needle.as_bytes())
101    }
102}
103
104impl std::borrow::Borrow<str> for StrView {
105    fn borrow(&self) -> &str {
106        self
107    }
108}
109
110impl AsRef<str> for StrView {
111    fn as_ref(&self) -> &str {
112        self
113    }
114}
115
116impl From<&str> for StrView {
117    fn from(value: &str) -> Self {
118        Self::new(value)
119    }
120}
121
122impl From<String> for StrView {
123    fn from(value: String) -> Self {
124        Self::new(&value)
125    }
126}
127
128impl From<Arc<str>> for StrView {
129    fn from(value: Arc<str>) -> Self {
130        Self::new(&value)
131    }
132}
133
134impl TryFrom<ByteView> for StrView {
135    type Error = std::str::Utf8Error;
136
137    fn try_from(value: ByteView) -> Result<Self, Self::Error> {
138        std::str::from_utf8(&value)?;
139        Ok(Self(value))
140    }
141}
142
143impl From<StrView> for ByteView {
144    fn from(val: StrView) -> Self {
145        val.0
146    }
147}
148
149#[cfg(feature = "serde")]
150mod serde {
151    use super::StrView;
152    use serde::de::{self, Visitor};
153    use serde::{Deserialize, Deserializer, Serialize, Serializer};
154    use std::fmt;
155    use std::ops::Deref;
156
157    impl Serialize for StrView {
158        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
159        where
160            S: Serializer,
161        {
162            serializer.serialize_str(self.deref())
163        }
164    }
165
166    impl<'de> Deserialize<'de> for StrView {
167        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
168        where
169            D: Deserializer<'de>,
170        {
171            struct StrViewVisitor;
172
173            impl<'de> Visitor<'de> for StrViewVisitor {
174                type Value = StrView;
175
176                fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
177                    formatter.write_str("a string")
178                }
179
180                fn visit_str<E>(self, v: &str) -> Result<StrView, E>
181                where
182                    E: de::Error,
183                {
184                    Ok(StrView::new(v))
185                }
186            }
187
188            deserializer.deserialize_bytes(StrViewVisitor)
189        }
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::StrView;
196    use std::collections::HashMap;
197
198    #[test]
199    fn strview_hash() {
200        let a = StrView::from("abcdef");
201
202        let mut map = HashMap::new();
203        map.insert(a, 0);
204        assert!(map.contains_key("abcdef"));
205    }
206
207    #[test]
208    fn cmp_misc_1() {
209        let a = StrView::from("abcdef");
210        let b = StrView::from("abcdefhelloworldhelloworld");
211        assert!(a < b);
212    }
213
214    #[test]
215    fn nostr() {
216        let slice = StrView::from("");
217        assert_eq!(0, slice.len());
218        assert_eq!(&*slice, "");
219    }
220
221    #[test]
222    fn default_str() {
223        let slice = StrView::default();
224        assert_eq!(0, slice.len());
225        assert_eq!(&*slice, "");
226    }
227
228    #[test]
229    fn short_str() {
230        let slice = StrView::from("abcdef");
231        assert_eq!(6, slice.len());
232        assert_eq!(&*slice, "abcdef");
233    }
234
235    #[test]
236    #[cfg(target_pointer_width = "64")]
237    fn medium_str() {
238        let slice = StrView::from("abcdefabcdef");
239        assert_eq!(12, slice.len());
240        assert_eq!(&*slice, "abcdefabcdef");
241    }
242
243    #[test]
244    #[cfg(target_pointer_width = "64")]
245    fn medium_long_str() {
246        let slice = StrView::from("abcdefabcdefabcdabcd");
247        assert_eq!(20, slice.len());
248        assert_eq!(&*slice, "abcdefabcdefabcdabcd");
249    }
250
251    #[test]
252    #[cfg(target_pointer_width = "64")]
253    fn medium_str_clone() {
254        let slice = StrView::from("abcdefabcdefabcdefa");
255
256        #[allow(clippy::redundant_clone)]
257        let copy = slice.clone();
258
259        assert_eq!(slice, copy);
260    }
261
262    #[test]
263    fn long_str() {
264        let slice = StrView::from("abcdefabcdefabcdefababcd");
265        assert_eq!(24, slice.len());
266        assert_eq!(&*slice, "abcdefabcdefabcdefababcd");
267    }
268
269    #[test]
270    fn long_str_clone() {
271        let slice = StrView::from("abcdefabcdefabcdefababcd");
272
273        #[allow(clippy::redundant_clone)]
274        let copy = slice.clone();
275
276        assert_eq!(slice, copy);
277    }
278
279    #[test]
280    fn long_str_slice_full() {
281        let slice = StrView::from("helloworld_thisisalongstring");
282
283        let copy = slice.slice(..);
284        assert_eq!(copy, slice);
285    }
286
287    #[test]
288    #[cfg(target_pointer_width = "64")]
289    fn long_str_slice() {
290        let slice = StrView::from("helloworld_thisisalongstring");
291
292        let copy = slice.slice(11..);
293        assert_eq!("thisisalongstring", &*copy);
294    }
295
296    #[test]
297    #[cfg(target_pointer_width = "64")]
298    fn long_str_slice_twice() {
299        let slice = StrView::from("helloworld_thisisalongstring");
300
301        let copy = slice.slice(11..);
302        assert_eq!("thisisalongstring", &*copy);
303
304        let copycopy = copy.slice(..);
305        assert_eq!(copy, copycopy);
306    }
307
308    #[test]
309    #[cfg(target_pointer_width = "64")]
310    fn long_str_slice_downgrade() {
311        let slice = StrView::from("helloworld_thisisalongstring");
312
313        let copy = slice.slice(11..);
314        assert_eq!("thisisalongstring", &*copy);
315
316        let copycopy = copy.slice(0..4);
317        assert_eq!("this", &*copycopy);
318
319        {
320            let copycopy = copy.slice(0..=4);
321            assert_eq!("thisi", &*copycopy);
322            assert_eq!('t', copycopy.chars().next().unwrap());
323        }
324    }
325
326    #[test]
327    fn short_str_clone() {
328        let slice = StrView::from("abcdef");
329        let copy = slice.clone();
330        assert_eq!(slice, copy);
331
332        drop(slice);
333        assert_eq!(&*copy, "abcdef");
334    }
335
336    #[test]
337    fn short_str_slice_full() {
338        let slice = StrView::from("abcdef");
339        let copy = slice.slice(..);
340        assert_eq!(slice, copy);
341
342        drop(slice);
343        assert_eq!(&*copy, "abcdef");
344    }
345
346    #[test]
347    fn short_str_slice_part() {
348        let slice = StrView::from("abcdef");
349        let copy = slice.slice(3..);
350
351        drop(slice);
352        assert_eq!(&*copy, "def");
353    }
354
355    #[test]
356    fn short_str_slice_empty() {
357        let slice = StrView::from("abcdef");
358        let copy = slice.slice(0..0);
359
360        drop(slice);
361        assert_eq!(&*copy, "");
362    }
363
364    #[test]
365    fn tiny_str_starts_with() {
366        let a = StrView::from("abc");
367        assert!(a.starts_with("ab"));
368        assert!(!a.starts_with("b"));
369    }
370
371    #[test]
372    fn long_str_starts_with() {
373        let a = StrView::from("abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef");
374        assert!(a.starts_with("abcdef"));
375        assert!(!a.starts_with("def"));
376    }
377
378    #[test]
379    fn tiny_str_cmp() {
380        let a = StrView::from("abc");
381        let b = StrView::from("def");
382        assert!(a < b);
383    }
384
385    #[test]
386    fn tiny_str_eq() {
387        let a = StrView::from("abc");
388        let b = StrView::from("def");
389        assert!(a != b);
390    }
391
392    #[test]
393    fn long_str_eq() {
394        let a = StrView::from("abcdefabcdefabcdefabcdef");
395        let b = StrView::from("xycdefabcdefabcdefabcdef");
396        assert!(a != b);
397    }
398
399    #[test]
400    fn long_str_cmp() {
401        let a = StrView::from("abcdefabcdefabcdefabcdef");
402        let b = StrView::from("xycdefabcdefabcdefabcdef");
403        assert!(a < b);
404    }
405
406    #[test]
407    fn long_str_eq_2() {
408        let a = StrView::from("abcdefabcdefabcdefabcdef");
409        let b = StrView::from("abcdefabcdefabcdefabcdef");
410        assert!(a == b);
411    }
412
413    #[test]
414    fn long_str_cmp_2() {
415        let a = StrView::from("abcdefabcdefabcdefabcdef");
416        let b = StrView::from("abcdefabcdefabcdefabcdeg");
417        assert!(a < b);
418    }
419
420    #[test]
421    fn long_str_cmp_3() {
422        let a = StrView::from("abcdefabcdefabcdefabcde");
423        let b = StrView::from("abcdefabcdefabcdefabcdef");
424        assert!(a < b);
425    }
426}