Skip to main content

jetro_core/
strref.rs

1//! Borrowed-slice string view that shares a parent `Arc<str>`.
2//!
3//! `StrRef` is a lightweight wrapper around an owning `Arc<str>` plus
4//! byte-range offsets.  Cloning it is an atomic Arc bump plus two
5//! word-sized copies — no heap allocation — so methods like `slice`,
6//! `split.first`, `substring` can return a view into their input
7//! without allocating a fresh `Arc<str>` per row.
8//!
9//! Invariants (enforced at construction):
10//! - `start <= end <= parent.len()`.
11//! - `parent[start..end]` is valid UTF-8 (the parent is, and we never
12//!   split between code units — callers construct slices via
13//!   `str::char_indices` or ASCII-only byte offsets).
14//!
15//! `as_str()` returns the view slice.  `Deref<Target=str>` /
16//! `AsRef<str>` allow existing string APIs (len, chars, find, memchr,
17//! etc.) to work against a `StrRef` transparently.
18
19use std::sync::Arc;
20
21#[derive(Clone, Debug)]
22pub struct StrRef {
23    parent: Arc<str>,
24    start: u32,
25    end: u32,
26}
27
28impl StrRef {
29    /// Construct a full view of `parent`.  Offsets = [0, parent.len()].
30    #[inline]
31    pub fn from_arc(parent: Arc<str>) -> Self {
32        let end = parent.len() as u32;
33        Self { parent, start: 0, end }
34    }
35
36    /// Byte-range view into `parent`.  Caller must ensure the range is
37    /// a valid UTF-8 boundary (checked in debug via `is_char_boundary`).
38    #[inline]
39    pub fn slice(parent: Arc<str>, start: usize, end: usize) -> Self {
40        debug_assert!(start <= end);
41        debug_assert!(end <= parent.len());
42        debug_assert!(parent.is_char_boundary(start));
43        debug_assert!(parent.is_char_boundary(end));
44        Self {
45            parent,
46            start: start as u32,
47            end: end as u32,
48        }
49    }
50
51    #[inline] pub fn as_str(&self) -> &str {
52        &self.parent[self.start as usize .. self.end as usize]
53    }
54
55    #[inline] pub fn len(&self) -> usize { (self.end - self.start) as usize }
56    #[inline] pub fn is_empty(&self) -> bool { self.end == self.start }
57
58    /// Produce an owning `Arc<str>` — allocates a fresh buffer containing
59    /// the view contents.  Use only when an owning Arc is required (e.g.
60    /// to insert into an `IndexMap<Arc<str>, Val>` as a key).
61    #[inline]
62    pub fn to_arc(&self) -> Arc<str> {
63        if self.start == 0 && self.end as usize == self.parent.len() {
64            Arc::clone(&self.parent)
65        } else {
66            Arc::<str>::from(self.as_str())
67        }
68    }
69}
70
71impl AsRef<str> for StrRef {
72    #[inline] fn as_ref(&self) -> &str { self.as_str() }
73}
74
75impl std::ops::Deref for StrRef {
76    type Target = str;
77    #[inline] fn deref(&self) -> &str { self.as_str() }
78}
79
80impl std::fmt::Display for StrRef {
81    #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        f.write_str(self.as_str())
83    }
84}
85
86impl PartialEq for StrRef {
87    #[inline] fn eq(&self, other: &Self) -> bool { self.as_str() == other.as_str() }
88}
89impl Eq for StrRef {}
90
91impl PartialEq<str> for StrRef {
92    #[inline] fn eq(&self, other: &str) -> bool { self.as_str() == other }
93}
94impl PartialEq<&str> for StrRef {
95    #[inline] fn eq(&self, other: &&str) -> bool { self.as_str() == *other }
96}
97impl PartialEq<Arc<str>> for StrRef {
98    #[inline] fn eq(&self, other: &Arc<str>) -> bool { self.as_str() == other.as_ref() }
99}
100
101impl std::hash::Hash for StrRef {
102    #[inline] fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
103        self.as_str().hash(state)
104    }
105}
106
107impl From<Arc<str>> for StrRef {
108    #[inline] fn from(a: Arc<str>) -> Self { Self::from_arc(a) }
109}
110impl From<&str> for StrRef {
111    #[inline] fn from(s: &str) -> Self { Self::from_arc(Arc::<str>::from(s)) }
112}
113impl From<String> for StrRef {
114    #[inline] fn from(s: String) -> Self { Self::from_arc(Arc::<str>::from(s)) }
115}