Skip to content

Commit 22ce205

Browse files
committed
Add SharedString
Written with the intention of replacing RawValue. No additional unsafe code was required for this as it's just a tagged union of &'static str and Arc<str> with optional source tracking.
1 parent 0cab01f commit 22ce205

2 files changed

Lines changed: 256 additions & 0 deletions

File tree

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ mod properties;
2929
pub mod property;
3030
pub mod rawvalue;
3131
mod section;
32+
pub mod string;
3233
#[cfg(test)]
3334
mod tests;
3435
mod traits;

src/string.rs

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
//! The `SharedString` type and supporting types.
2+
3+
use std::borrow::Cow;
4+
5+
#[cfg(feature = "track-source")]
6+
mod source {
7+
#[derive(Clone)]
8+
pub struct Source {
9+
path: std::sync::Arc<std::path::Path>,
10+
line: usize,
11+
}
12+
13+
impl std::fmt::Debug for Source {
14+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15+
write!(f, "{}:{}", self.path.to_string_lossy(), self.line)
16+
}
17+
}
18+
19+
impl Source {
20+
pub fn new(path: &std::path::Path, line: usize) -> Self {
21+
Source {
22+
path: std::sync::Arc::from(path),
23+
line,
24+
}
25+
}
26+
pub fn get(&self) -> (&std::path::Path, usize) {
27+
(std::sync::Arc::as_ref(&self.path), self.line)
28+
}
29+
}
30+
}
31+
32+
// TODO: Eventually add support for an Arc-like type that uses a thin pointer here.
33+
// Probably not triomphe::ThinArc, since we'd need to use unsafe to use it.
34+
35+
#[derive(Clone, Debug)]
36+
enum SharedStringInner {
37+
Static(&'static str),
38+
Owned(std::sync::Arc<str>),
39+
}
40+
41+
impl SharedStringInner {
42+
#[inline]
43+
pub fn new(string: &str) -> Self {
44+
SharedStringInner::Owned(std::sync::Arc::from(string))
45+
}
46+
#[inline]
47+
pub fn get(&self) -> &str {
48+
match self {
49+
SharedStringInner::Static(v) => v,
50+
SharedStringInner::Owned(v) => std::sync::Arc::as_ref(v),
51+
}
52+
}
53+
}
54+
55+
impl Default for SharedStringInner {
56+
fn default() -> Self {
57+
SharedStringInner::Static("")
58+
}
59+
}
60+
61+
impl std::ops::Deref for SharedStringInner {
62+
type Target = str;
63+
64+
fn deref(&self) -> &Self::Target {
65+
self.get()
66+
}
67+
}
68+
69+
/// A shared immutable string type.
70+
///
71+
/// Internally, this is either a `&'static str` or an atomically ref-counted `str`.
72+
/// It's meant to represent either keys or values with minimal allocations
73+
/// or duplication of data in memory, in exchange for not supporting mutation
74+
/// or even in-place slicing.
75+
///
76+
/// With the `track-source` feature,
77+
/// objects of this type can also track the file and line number they originate from.
78+
#[derive(Clone, Debug)]
79+
pub struct SharedString {
80+
value: SharedStringInner,
81+
#[cfg(feature = "track-source")]
82+
source: Option<source::Source>,
83+
}
84+
85+
/// A `SharedString` equal to `"unset"`.
86+
pub static UNSET: SharedString = SharedString {
87+
value: SharedStringInner::Static("unset"),
88+
#[cfg(feature = "track-source")]
89+
source: None,
90+
};
91+
92+
/// A `SharedString` equal to `""` (an empty string).
93+
pub static EMPTY: SharedString = SharedString {
94+
value: SharedStringInner::Static(""),
95+
#[cfg(feature = "track-source")]
96+
source: None,
97+
};
98+
99+
// Manual-impl (Partial)Eq, (Partial)Ord, and Hash so that the source isn't considered.
100+
101+
impl PartialEq for SharedString {
102+
fn eq(&self, other: &Self) -> bool {
103+
*self.value == *other.value
104+
}
105+
}
106+
impl Eq for SharedString {}
107+
impl PartialOrd for SharedString {
108+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
109+
Some(self.value.cmp(&other.value))
110+
}
111+
}
112+
impl Ord for SharedString {
113+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
114+
self.value.cmp(&other.value)
115+
}
116+
}
117+
impl std::hash::Hash for SharedString {
118+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
119+
state.write(self.value.as_bytes());
120+
state.write_u8(0);
121+
}
122+
}
123+
124+
impl std::borrow::Borrow<str> for SharedString {
125+
fn borrow(&self) -> &str {
126+
&self.value
127+
}
128+
}
129+
130+
impl AsRef<str> for SharedString {
131+
fn as_ref(&self) -> &str {
132+
&self.value
133+
}
134+
}
135+
impl AsRef<[u8]> for SharedString {
136+
fn as_ref(&self) -> &[u8] {
137+
self.value.as_bytes()
138+
}
139+
}
140+
141+
impl std::ops::Deref for SharedString {
142+
type Target = str;
143+
144+
fn deref(&self) -> &Self::Target {
145+
&*self.value
146+
}
147+
}
148+
149+
impl Default for SharedString {
150+
fn default() -> Self {
151+
EMPTY.clone()
152+
}
153+
}
154+
155+
impl Default for &SharedString {
156+
fn default() -> Self {
157+
&EMPTY
158+
}
159+
}
160+
161+
impl SharedString {
162+
/// Creates `Self` from the provided string.
163+
///
164+
/// This function copies the string. If the string is `'static`, consider
165+
/// using [`SharedString::new_static`] instead.
166+
pub fn new(value: impl AsRef<str>) -> Self {
167+
SharedString {
168+
value: SharedStringInner::new(value.as_ref()),
169+
#[cfg(feature = "track-source")]
170+
source: None,
171+
}
172+
}
173+
/// As [`SharedString::new`] but only accepts a `&'static str`.
174+
///
175+
/// This function does not copy the string.
176+
pub fn new_static(value: &'static str) -> Self {
177+
SharedString {
178+
value: SharedStringInner::Static(value),
179+
#[cfg(feature = "track-source")]
180+
source: None,
181+
}
182+
}
183+
#[cfg(feature = "track-source")]
184+
/// Returns the path to the file and the line number that this value originates from.
185+
///
186+
/// The line number is 1-indexed to match convention;
187+
/// the first line will have a line number of 1 rather than 0.
188+
pub fn source(&self) -> Option<(&std::path::Path, usize)> {
189+
self.source.as_ref().map(source::Source::get)
190+
}
191+
192+
#[cfg(feature = "track-source")]
193+
/// Sets the path and line number from which this value originated.
194+
///
195+
/// The line number should be 1-indexed to match convention;
196+
/// the first line should have a line number of 1 rather than 0.
197+
pub fn set_source(&mut self, path: impl AsRef<std::path::Path>, line: usize) {
198+
self.source = Some(source::Source::new(path.as_ref(), line))
199+
}
200+
201+
#[cfg(feature = "track-source")]
202+
/// Efficiently clones the source from `other`.
203+
pub fn set_source_from(&mut self, other: &SharedString) {
204+
self.source = other.source.clone();
205+
}
206+
207+
/// Clears the path and line number from which this value originated.
208+
///
209+
/// If the `track-source` feature is not enabled, this function is a no-op.
210+
pub fn clear_source(&mut self) {
211+
#[cfg(feature = "track-source")]
212+
{
213+
self.source = None;
214+
}
215+
}
216+
217+
/// Returns a lowercased version of `self`. Will not allocate if `self` is already lowercase.
218+
#[must_use]
219+
pub fn into_lowercase(&self) -> Self {
220+
// TODO: This requires two iterations over the string, and it's definitely possible
221+
// to do it in one.
222+
match into_lowercase(self.value.as_ref()) {
223+
Cow::Borrowed(_) => self.clone(),
224+
Cow::Owned(v) => {
225+
#[allow(unused_mut)]
226+
let mut retval = SharedString::new(v);
227+
#[cfg(feature = "track-source")]
228+
retval.set_source_from(self);
229+
retval
230+
}
231+
}
232+
}
233+
}
234+
235+
impl std::fmt::Display for SharedString {
236+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237+
write!(f, "{}", &*self.value)
238+
}
239+
}
240+
241+
impl From<&str> for SharedString {
242+
fn from(value: &str) -> Self {
243+
Self::new(value)
244+
}
245+
}
246+
247+
pub(crate) fn into_lowercase(string: &str) -> std::borrow::Cow<str> {
248+
// TODO: This requires two iterations over the string, and it's definitely possible
249+
// to do it in one.
250+
if string.chars().all(char::is_lowercase) {
251+
std::borrow::Cow::Borrowed(string)
252+
} else {
253+
std::borrow::Cow::Owned(string.to_lowercase())
254+
}
255+
}

0 commit comments

Comments
 (0)