├── .gitignore ├── Cargo.toml ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "smallstring" 3 | version = "0.1.2" 4 | authors = ["Jack Fransham "] 5 | repository = "https://github.com/jFransham/smallstring" 6 | description = "'Small string' optimization: store small strings on the stack using smallvec" 7 | license = "MIT" 8 | 9 | [features] 10 | default = [] 11 | as-mut = [] 12 | 13 | [dependencies] 14 | smallvec = "0.3" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Small String 2 | 3 | Create strings of any length on the stack, automatically upgrading to the heap 4 | when they become larger than the buffer. Also allows converting from a `String` 5 | for free (i.e. without copying to the stack even if they're small enough). 6 | 7 | Backed by [`smallvec`](https://crates.io/crates/smallvec). 8 | 9 | ```rust 10 | // Default maximum size to store on the stack: 8 bytes 11 | let stack: SmallString = "Hello!".into(); 12 | 13 | // Reuses allocation 14 | let heap: String = "Hello!".into(); 15 | let still_heap: SmallString = heap.into(); 16 | ``` 17 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature="as-mut", feature(str_mut_extras))] 2 | 3 | extern crate smallvec; 4 | 5 | use std::str; 6 | use std::ffi::OsStr; 7 | use std::ops::Deref; 8 | use std::borrow::Borrow; 9 | use std::iter::{FromIterator, IntoIterator}; 10 | use smallvec::{Array, SmallVec}; 11 | 12 | // TODO: FromIterator without having to allocate a String 13 | #[derive(Clone, Default)] 14 | pub struct SmallString = [u8; 8]> { 15 | buffer: SmallVec, 16 | } 17 | 18 | impl> std::hash::Hash for SmallString { 19 | fn hash(&self, state: &mut H) { 20 | let s: &str = self; 21 | s.hash(state) 22 | } 23 | } 24 | 25 | impl> std::cmp::PartialEq for SmallString { 26 | fn eq(&self, other: &Self) -> bool { 27 | let (s1, s2): (&str, &str) = (self, other); 28 | s1 == s2 29 | } 30 | } 31 | 32 | impl> std::cmp::Eq for SmallString {} 33 | 34 | impl<'a, B: Array> PartialEq> for &'a str { 35 | fn eq(&self, other: &SmallString) -> bool { 36 | *self == (other as &str) 37 | } 38 | } 39 | 40 | impl> std::fmt::Display for SmallString { 41 | fn fmt(&self, fm: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 42 | let s: &str = SmallString::deref(self); 43 | s.fmt(fm) 44 | } 45 | } 46 | 47 | impl> std::fmt::Debug for SmallString { 48 | fn fmt(&self, fm: &mut std::fmt::Formatter) -> std::fmt::Result { 49 | let s: &str = SmallString::deref(self); 50 | s.fmt(fm) 51 | } 52 | } 53 | 54 | impl> SmallString { 55 | pub fn from_str(s: &str) -> Self { 56 | SmallString { 57 | buffer: s.as_bytes().into_iter() 58 | .cloned() 59 | .collect(), 60 | } 61 | } 62 | } 63 | 64 | impl<'a, B: Array> From<&'a str> for SmallString { 65 | fn from(s: &str) -> Self { 66 | Self::from_str(s) 67 | } 68 | } 69 | 70 | impl> Deref for SmallString { 71 | type Target = str; 72 | 73 | fn deref(&self) -> &str { 74 | // We only allow `buffer` to be created from an existing valid string, 75 | // so this is safe. 76 | unsafe { 77 | str::from_utf8_unchecked(self.buffer.as_ref()) 78 | } 79 | } 80 | } 81 | 82 | impl AsRef for SmallString { 83 | fn as_ref(&self) -> &str { 84 | // We only allow `buffer` to be created from an existing valid string, 85 | // so this is safe. 86 | unsafe { 87 | str::from_utf8_unchecked(self.buffer.as_ref()) 88 | } 89 | } 90 | } 91 | 92 | struct Utf8Iterator(I, Option>); 93 | 94 | impl> Utf8Iterator { 95 | pub fn new>(into: In) -> Self { 96 | Utf8Iterator(into.into_iter(), None) 97 | } 98 | } 99 | 100 | impl> Iterator for Utf8Iterator { 101 | type Item = u8; 102 | 103 | fn next(&mut self) -> Option { 104 | if let Some(mut into) = self.1.take() { 105 | if let Some(n) = into.next() { 106 | self.1 = Some(into); 107 | return Some(n); 108 | } 109 | } 110 | 111 | let out = self.0.next(); 112 | 113 | out.and_then(|chr| { 114 | let mut dest = [0u8; 4]; 115 | let outstr = chr.encode_utf8(&mut dest); 116 | 117 | self.1 = Some( 118 | outstr.as_bytes() 119 | .into_iter() 120 | .cloned() 121 | .collect::>() 122 | .into_iter() 123 | ); 124 | 125 | self.1.as_mut().and_then(|i| i.next()) 126 | }) 127 | } 128 | 129 | fn size_hint(&self) -> (usize, Option) { 130 | let hint = self.0.size_hint(); 131 | 132 | (hint.0, hint.1.map(|x| x * 4)) 133 | } 134 | } 135 | 136 | impl FromIterator for SmallString { 137 | fn from_iter>(into_iter: T) -> Self { 138 | // We're a shell so we mostly work with ASCII data - optimise for this 139 | // case since we have to optimise for _some_ fixed size of char. 140 | let utf8 = Utf8Iterator::new(into_iter); 141 | 142 | SmallString { 143 | buffer: utf8.collect(), 144 | } 145 | } 146 | } 147 | 148 | #[cfg(feature="as-mut")] 149 | impl AsMut for SmallString { 150 | fn as_mut(&mut self) -> &mut str { 151 | // We only allow `buffer` to be created from an existing valid string, 152 | // so this is safe. 153 | unsafe { 154 | str::from_utf8_unchecked_mut(self.buffer.as_mut()) 155 | } 156 | } 157 | } 158 | 159 | impl AsRef for SmallString { 160 | fn as_ref(&self) -> &OsStr { 161 | let s: &str = self.as_ref(); 162 | s.as_ref() 163 | } 164 | } 165 | 166 | impl Borrow for SmallString { 167 | fn borrow(&self) -> &str { 168 | // We only allow `buffer` to be created from an existing valid string, 169 | // so this is safe. 170 | unsafe { 171 | str::from_utf8_unchecked(self.buffer.as_ref()) 172 | } 173 | } 174 | } 175 | 176 | impl From for SmallString { 177 | fn from(s: String) -> SmallString { 178 | SmallString { 179 | buffer: SmallVec::from_vec(s.into_bytes()), 180 | } 181 | } 182 | } 183 | 184 | impl From for String { 185 | fn from(s: SmallString) -> String { 186 | unsafe { 187 | String::from_utf8_unchecked(s.buffer.into_vec()) 188 | } 189 | } 190 | } 191 | --------------------------------------------------------------------------------