diff options
Diffstat (limited to 'crates/smol_str')
-rw-r--r-- | crates/smol_str/Cargo.toml | 6 | ||||
-rw-r--r-- | crates/smol_str/src/lib.rs | 132 |
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] | ||
2 | name = "smol_str" | ||
3 | version = "0.1.0" | ||
4 | authors = ["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 @@ | |||
1 | use std::{sync::Arc, ops::Deref}; | ||
2 | |||
3 | #[derive(Clone, Debug)] | ||
4 | pub struct SmolStr(Repr); | ||
5 | |||
6 | impl 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 | |||
20 | impl Deref for SmolStr { | ||
21 | type Target = str; | ||
22 | |||
23 | fn deref(&self) -> &str { | ||
24 | self.as_str() | ||
25 | } | ||
26 | } | ||
27 | |||
28 | impl PartialEq<str> for SmolStr { | ||
29 | fn eq(&self, other: &str) -> bool { | ||
30 | self.as_str() == other | ||
31 | } | ||
32 | } | ||
33 | |||
34 | impl PartialEq<SmolStr> for str { | ||
35 | fn eq(&self, other: &SmolStr) -> bool { | ||
36 | other == self | ||
37 | } | ||
38 | } | ||
39 | |||
40 | impl<'a> PartialEq<&'a str> for SmolStr { | ||
41 | fn eq(&self, other: &&'a str) -> bool { | ||
42 | self == *other | ||
43 | } | ||
44 | } | ||
45 | |||
46 | impl<'a> PartialEq<SmolStr> for &'a str { | ||
47 | fn eq(&self, other: &SmolStr) -> bool { | ||
48 | *self == other | ||
49 | } | ||
50 | } | ||
51 | |||
52 | const INLINE_CAP: usize = 22; | ||
53 | const WS_TAG: u8 = (INLINE_CAP + 1) as u8; | ||
54 | |||
55 | #[derive(Clone, Debug)] | ||
56 | enum Repr { | ||
57 | Heap(Arc<str>), | ||
58 | Inline { | ||
59 | len: u8, | ||
60 | buf: [u8; INLINE_CAP], | ||
61 | }, | ||
62 | } | ||
63 | |||
64 | impl 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 | |||
106 | const N_NEWLINES: usize = 32; | ||
107 | const N_SPACES: usize = 128; | ||
108 | const 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)] | ||
113 | mod 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 | |||