aboutsummaryrefslogtreecommitdiff
path: root/crates/smol_str
diff options
context:
space:
mode:
Diffstat (limited to 'crates/smol_str')
-rw-r--r--crates/smol_str/Cargo.toml6
-rw-r--r--crates/smol_str/src/lib.rs132
2 files changed, 138 insertions, 0 deletions
diff --git a/crates/smol_str/Cargo.toml b/crates/smol_str/Cargo.toml
new file mode 100644
index 000000000..83ca12f62
--- /dev/null
+++ b/crates/smol_str/Cargo.toml
@@ -0,0 +1,6 @@
1[package]
2name = "smol_str"
3version = "0.1.0"
4authors = ["Aleksey Kladov <[email protected]>"]
5
6[dependencies]
diff --git a/crates/smol_str/src/lib.rs b/crates/smol_str/src/lib.rs
new file mode 100644
index 000000000..4d5fef593
--- /dev/null
+++ b/crates/smol_str/src/lib.rs
@@ -0,0 +1,132 @@
1use std::{sync::Arc, ops::Deref};
2
3#[derive(Clone, Debug)]
4pub struct SmolStr(Repr);
5
6impl SmolStr {
7 pub fn new(text: &str) -> SmolStr {
8 SmolStr(Repr::new(text))
9 }
10
11 pub fn as_str(&self) -> &str {
12 self.0.as_str()
13 }
14
15 pub fn to_string(&self) -> String {
16 self.as_str().to_string()
17 }
18}
19
20impl Deref for SmolStr {
21 type Target = str;
22
23 fn deref(&self) -> &str {
24 self.as_str()
25 }
26}
27
28impl PartialEq<str> for SmolStr {
29 fn eq(&self, other: &str) -> bool {
30 self.as_str() == other
31 }
32}
33
34impl PartialEq<SmolStr> for str {
35 fn eq(&self, other: &SmolStr) -> bool {
36 other == self
37 }
38}
39
40impl<'a> PartialEq<&'a str> for SmolStr {
41 fn eq(&self, other: &&'a str) -> bool {
42 self == *other
43 }
44}
45
46impl<'a> PartialEq<SmolStr> for &'a str {
47 fn eq(&self, other: &SmolStr) -> bool {
48 *self == other
49 }
50}
51
52const INLINE_CAP: usize = 22;
53const WS_TAG: u8 = (INLINE_CAP + 1) as u8;
54
55#[derive(Clone, Debug)]
56enum Repr {
57 Heap(Arc<str>),
58 Inline {
59 len: u8,
60 buf: [u8; INLINE_CAP],
61 },
62}
63
64impl Repr {
65 fn new(text: &str) -> Repr {
66 let len = text.len();
67 if len <= INLINE_CAP {
68 let mut buf = [0; INLINE_CAP];
69 buf[..len].copy_from_slice(text.as_bytes());
70 return Repr::Inline { len: len as u8, buf };
71 }
72
73 let newlines = text.bytes().take_while(|&b| b == b'\n').count();
74 let spaces = text[newlines..].bytes().take_while(|&b| b == b' ').count();
75 if newlines + spaces == len && newlines <= N_NEWLINES && spaces <= N_SPACES {
76 let mut buf = [0; INLINE_CAP];
77 buf[0] = newlines as u8;
78 buf[1] = spaces as u8;
79 return Repr::Inline { len: WS_TAG, buf };
80 }
81
82 Repr::Heap(
83 text.to_string().into_boxed_str().into()
84 )
85 }
86
87 fn as_str(&self) -> &str {
88 match self {
89 Repr::Heap(data) => &*data,
90 Repr::Inline { len, buf } => {
91 if *len == WS_TAG {
92 let newlines = buf[0] as usize;
93 let spaces = buf[1] as usize;
94 assert!(newlines <= N_NEWLINES && spaces <= N_SPACES);
95 return &WS[N_NEWLINES - newlines..N_NEWLINES + spaces];
96 }
97
98 let len = *len as usize;
99 let buf = &buf[..len];
100 unsafe { ::std::str::from_utf8_unchecked(buf) }
101 }
102 }
103 }
104}
105
106const N_NEWLINES: usize = 32;
107const N_SPACES: usize = 128;
108const WS: &str =
109 "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n ";
110
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 #[cfg(target_pointer_width = "64")]
118 fn smol_str_is_smol() {
119 assert_eq!(::std::mem::size_of::<SmolStr>(), 8 + 8 + 8)
120 }
121
122 #[test]
123 fn test_round_trip() {
124 let mut text = String::new();
125 for n in 0..256 {
126 let smol = SmolStr::new(&text);
127 assert_eq!(smol.as_str(), text.as_str());
128 text.push_str(&n.to_string());
129 }
130 }
131}
132