1#![no_std]
2extern crate alloc;
3
4use alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc};
5use core::{
6 borrow::Borrow,
7 cmp::{self, Ordering},
8 convert::Infallible,
9 fmt, hash, iter,
10 mem::transmute,
11 ops::Deref,
12 str::FromStr,
13};
14
15pub struct SmolStr(Repr);
34
35impl Clone for SmolStr {
36 #[inline]
37 fn clone(&self) -> Self {
38 if !self.is_heap_allocated() {
39 return unsafe { core::ptr::read(self as *const SmolStr) };
40 }
41 Self(self.0.clone())
42 }
43}
44
45impl SmolStr {
46 #[deprecated = "Use `new_inline` instead"]
47 pub const fn new_inline_from_ascii(len: usize, bytes: &[u8]) -> SmolStr {
48 assert!(len <= INLINE_CAP);
49
50 const ZEROS: &[u8] = &[0; INLINE_CAP];
51
52 let mut buf = [0; INLINE_CAP];
53 macro_rules! s {
54 ($($idx:literal),*) => ( $(s!(set $idx);)* );
55 (set $idx:literal) => ({
56 let src: &[u8] = [ZEROS, bytes][($idx < len) as usize];
57 let byte = src[$idx];
58 let _is_ascii = [(); 128][byte as usize];
59 buf[$idx] = byte
60 });
61 }
62 s!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22);
63 SmolStr(Repr::Inline {
64 len: unsafe { InlineSize::transmute_from_u8(len as u8) },
67 buf,
68 })
69 }
70
71 #[inline]
75 pub const fn new_inline(text: &str) -> SmolStr {
76 assert!(text.len() <= INLINE_CAP); let mut buf = [0; INLINE_CAP];
79 let mut i = 0;
80 while i < text.len() {
81 buf[i] = text.as_bytes()[i];
82 i += 1
83 }
84 SmolStr(Repr::Inline {
85 len: unsafe { InlineSize::transmute_from_u8(text.len() as u8) },
88 buf,
89 })
90 }
91
92 #[inline(always)]
96 pub const fn new_static(text: &'static str) -> SmolStr {
97 SmolStr(Repr::Static(text))
101 }
102
103 pub fn new<T>(text: T) -> SmolStr
104 where
105 T: AsRef<str>,
106 {
107 SmolStr(Repr::new(text))
108 }
109
110 #[inline(always)]
111 pub fn as_str(&self) -> &str {
112 self.0.as_str()
113 }
114
115 #[allow(clippy::inherent_to_string_shadow_display)]
116 #[inline(always)]
117 pub fn to_string(&self) -> String {
118 use alloc::borrow::ToOwned;
119
120 self.as_str().to_owned()
121 }
122
123 #[inline(always)]
124 pub fn len(&self) -> usize {
125 self.0.len()
126 }
127
128 #[inline(always)]
129 pub fn is_empty(&self) -> bool {
130 self.0.is_empty()
131 }
132
133 #[inline(always)]
134 pub const fn is_heap_allocated(&self) -> bool {
135 matches!(self.0, Repr::Heap(..))
136 }
137
138 fn from_char_iter<I: iter::Iterator<Item = char>>(mut iter: I) -> SmolStr {
139 let (min_size, _) = iter.size_hint();
140 if min_size > INLINE_CAP {
141 let heap: String = iter.collect();
142 return SmolStr(Repr::Heap(heap.into_boxed_str().into()));
143 }
144 let mut len = 0;
145 let mut buf = [0u8; INLINE_CAP];
146 while let Some(ch) = iter.next() {
147 let size = ch.len_utf8();
148 if size + len > INLINE_CAP {
149 let (min_remaining, _) = iter.size_hint();
150 let mut heap = String::with_capacity(size + len + min_remaining);
151 heap.push_str(core::str::from_utf8(&buf[..len]).unwrap());
152 heap.push(ch);
153 heap.extend(iter);
154 return SmolStr(Repr::Heap(heap.into_boxed_str().into()));
155 }
156 ch.encode_utf8(&mut buf[len..]);
157 len += size;
158 }
159 SmolStr(Repr::Inline {
160 len: unsafe { InlineSize::transmute_from_u8(len as u8) },
163 buf,
164 })
165 }
166}
167
168impl Default for SmolStr {
169 #[inline(always)]
170 fn default() -> SmolStr {
171 SmolStr(Repr::Inline {
172 len: InlineSize::_V0,
173 buf: [0; INLINE_CAP],
174 })
175 }
176}
177
178impl Deref for SmolStr {
179 type Target = str;
180
181 #[inline(always)]
182 fn deref(&self) -> &str {
183 self.as_str()
184 }
185}
186
187impl PartialEq<SmolStr> for SmolStr {
188 fn eq(&self, other: &SmolStr) -> bool {
189 self.0.ptr_eq(&other.0) || self.as_str() == other.as_str()
190 }
191}
192
193impl Eq for SmolStr {}
194
195impl PartialEq<str> for SmolStr {
196 fn eq(&self, other: &str) -> bool {
197 self.as_str() == other
198 }
199}
200
201impl PartialEq<SmolStr> for str {
202 fn eq(&self, other: &SmolStr) -> bool {
203 other == self
204 }
205}
206
207impl<'a> PartialEq<&'a str> for SmolStr {
208 fn eq(&self, other: &&'a str) -> bool {
209 self == *other
210 }
211}
212
213impl<'a> PartialEq<SmolStr> for &'a str {
214 fn eq(&self, other: &SmolStr) -> bool {
215 *self == other
216 }
217}
218
219impl PartialEq<String> for SmolStr {
220 fn eq(&self, other: &String) -> bool {
221 self.as_str() == other
222 }
223}
224
225impl PartialEq<SmolStr> for String {
226 fn eq(&self, other: &SmolStr) -> bool {
227 other == self
228 }
229}
230
231impl<'a> PartialEq<&'a String> for SmolStr {
232 fn eq(&self, other: &&'a String) -> bool {
233 self == *other
234 }
235}
236
237impl<'a> PartialEq<SmolStr> for &'a String {
238 fn eq(&self, other: &SmolStr) -> bool {
239 *self == other
240 }
241}
242
243impl Ord for SmolStr {
244 fn cmp(&self, other: &SmolStr) -> Ordering {
245 self.as_str().cmp(other.as_str())
246 }
247}
248
249impl PartialOrd for SmolStr {
250 fn partial_cmp(&self, other: &SmolStr) -> Option<Ordering> {
251 Some(self.cmp(other))
252 }
253}
254
255impl hash::Hash for SmolStr {
256 fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
257 self.as_str().hash(hasher);
258 }
259}
260
261impl fmt::Debug for SmolStr {
262 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
263 fmt::Debug::fmt(self.as_str(), f)
264 }
265}
266
267impl fmt::Display for SmolStr {
268 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
269 fmt::Display::fmt(self.as_str(), f)
270 }
271}
272
273impl iter::FromIterator<char> for SmolStr {
274 fn from_iter<I: iter::IntoIterator<Item = char>>(iter: I) -> SmolStr {
275 let iter = iter.into_iter();
276 Self::from_char_iter(iter)
277 }
278}
279
280fn build_from_str_iter<T>(mut iter: impl Iterator<Item = T>) -> SmolStr
281where
282 T: AsRef<str>,
283 String: iter::Extend<T>,
284{
285 let mut len = 0;
286 let mut buf = [0u8; INLINE_CAP];
287 while let Some(slice) = iter.next() {
288 let slice = slice.as_ref();
289 let size = slice.len();
290 if size + len > INLINE_CAP {
291 let mut heap = String::with_capacity(size + len);
292 heap.push_str(core::str::from_utf8(&buf[..len]).unwrap());
293 heap.push_str(slice);
294 heap.extend(iter);
295 return SmolStr(Repr::Heap(heap.into_boxed_str().into()));
296 }
297 buf[len..][..size].copy_from_slice(slice.as_bytes());
298 len += size;
299 }
300 SmolStr(Repr::Inline {
301 len: unsafe { InlineSize::transmute_from_u8(len as u8) },
304 buf,
305 })
306}
307
308impl iter::FromIterator<String> for SmolStr {
309 fn from_iter<I: iter::IntoIterator<Item = String>>(iter: I) -> SmolStr {
310 build_from_str_iter(iter.into_iter())
311 }
312}
313
314impl<'a> iter::FromIterator<&'a String> for SmolStr {
315 fn from_iter<I: iter::IntoIterator<Item = &'a String>>(iter: I) -> SmolStr {
316 SmolStr::from_iter(iter.into_iter().map(|x| x.as_str()))
317 }
318}
319
320impl<'a> iter::FromIterator<&'a str> for SmolStr {
321 fn from_iter<I: iter::IntoIterator<Item = &'a str>>(iter: I) -> SmolStr {
322 build_from_str_iter(iter.into_iter())
323 }
324}
325
326impl AsRef<str> for SmolStr {
327 #[inline(always)]
328 fn as_ref(&self) -> &str {
329 self.as_str()
330 }
331}
332
333impl From<&str> for SmolStr {
334 #[inline]
335 fn from(s: &str) -> SmolStr {
336 SmolStr::new(s)
337 }
338}
339
340impl From<&mut str> for SmolStr {
341 #[inline]
342 fn from(s: &mut str) -> SmolStr {
343 SmolStr::new(s)
344 }
345}
346
347impl From<&String> for SmolStr {
348 #[inline]
349 fn from(s: &String) -> SmolStr {
350 SmolStr::new(s)
351 }
352}
353
354impl From<String> for SmolStr {
355 #[inline(always)]
356 fn from(text: String) -> Self {
357 Self::new(text)
358 }
359}
360
361impl From<Box<str>> for SmolStr {
362 #[inline]
363 fn from(s: Box<str>) -> SmolStr {
364 SmolStr::new(s)
365 }
366}
367
368impl From<Arc<str>> for SmolStr {
369 #[inline]
370 fn from(s: Arc<str>) -> SmolStr {
371 let repr = Repr::new_on_stack(s.as_ref()).unwrap_or_else(|| Repr::Heap(s));
372 Self(repr)
373 }
374}
375
376impl<'a> From<Cow<'a, str>> for SmolStr {
377 #[inline]
378 fn from(s: Cow<'a, str>) -> SmolStr {
379 SmolStr::new(s)
380 }
381}
382
383impl From<SmolStr> for Arc<str> {
384 #[inline(always)]
385 fn from(text: SmolStr) -> Self {
386 match text.0 {
387 Repr::Heap(data) => data,
388 _ => text.as_str().into(),
389 }
390 }
391}
392
393impl From<SmolStr> for String {
394 #[inline(always)]
395 fn from(text: SmolStr) -> Self {
396 text.as_str().into()
397 }
398}
399
400impl Borrow<str> for SmolStr {
401 #[inline(always)]
402 fn borrow(&self) -> &str {
403 self.as_str()
404 }
405}
406
407impl FromStr for SmolStr {
408 type Err = Infallible;
409
410 #[inline]
411 fn from_str(s: &str) -> Result<SmolStr, Self::Err> {
412 Ok(SmolStr::from(s))
413 }
414}
415
416#[cfg(feature = "arbitrary")]
417impl<'a> arbitrary::Arbitrary<'a> for SmolStr {
418 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> Result<Self, arbitrary::Error> {
419 let s = <&str>::arbitrary(u)?;
420 Ok(SmolStr::new(s))
421 }
422}
423
424const INLINE_CAP: usize = InlineSize::_V23 as usize;
425const N_NEWLINES: usize = 32;
426const N_SPACES: usize = 128;
427const WS: &str =
428 "\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 ";
429const _: () = {
430 assert!(WS.len() == N_NEWLINES + N_SPACES);
431 assert!(WS.as_bytes()[N_NEWLINES - 1] == b'\n');
432 assert!(WS.as_bytes()[N_NEWLINES] == b' ');
433};
434
435#[derive(Clone, Copy, Debug, PartialEq)]
436#[repr(u8)]
437enum InlineSize {
438 _V0 = 0,
439 _V1,
440 _V2,
441 _V3,
442 _V4,
443 _V5,
444 _V6,
445 _V7,
446 _V8,
447 _V9,
448 _V10,
449 _V11,
450 _V12,
451 _V13,
452 _V14,
453 _V15,
454 _V16,
455 _V17,
456 _V18,
457 _V19,
458 _V20,
459 _V21,
460 _V22,
461 _V23,
462}
463
464impl InlineSize {
465 #[inline(always)]
466 const unsafe fn transmute_from_u8(value: u8) -> Self {
467 debug_assert!(value <= InlineSize::_V23 as u8);
468 unsafe { transmute::<u8, Self>(value) }
469 }
470}
471
472#[derive(Clone, Debug)]
473enum Repr {
474 Inline {
475 len: InlineSize,
476 buf: [u8; INLINE_CAP],
477 },
478 Static(&'static str),
479 Heap(Arc<str>),
480}
481
482impl Repr {
483 fn new_on_stack<T>(text: T) -> Option<Self>
486 where
487 T: AsRef<str>,
488 {
489 let text = text.as_ref();
490
491 let len = text.len();
492 if len <= INLINE_CAP {
493 let mut buf = [0; INLINE_CAP];
494 buf[..len].copy_from_slice(text.as_bytes());
495 return Some(Repr::Inline {
496 len: unsafe { InlineSize::transmute_from_u8(len as u8) },
498 buf,
499 });
500 }
501
502 if len <= N_NEWLINES + N_SPACES {
503 let bytes = text.as_bytes();
504 let possible_newline_count = cmp::min(len, N_NEWLINES);
505 let newlines = bytes[..possible_newline_count]
506 .iter()
507 .take_while(|&&b| b == b'\n')
508 .count();
509 let possible_space_count = len - newlines;
510 if possible_space_count <= N_SPACES && bytes[newlines..].iter().all(|&b| b == b' ') {
511 let spaces = possible_space_count;
512 let substring = &WS[N_NEWLINES - newlines..N_NEWLINES + spaces];
513 return Some(Repr::Static(substring));
514 }
515 }
516 None
517 }
518
519 fn new<T>(text: T) -> Self
520 where
521 T: AsRef<str>,
522 {
523 Self::new_on_stack(text.as_ref()).unwrap_or_else(|| Repr::Heap(text.as_ref().into()))
524 }
525
526 #[inline(always)]
527 fn len(&self) -> usize {
528 match self {
529 Repr::Heap(data) => data.len(),
530 Repr::Static(data) => data.len(),
531 Repr::Inline { len, .. } => *len as usize,
532 }
533 }
534
535 #[inline(always)]
536 fn is_empty(&self) -> bool {
537 match self {
538 Repr::Heap(data) => data.is_empty(),
539 Repr::Static(data) => data.is_empty(),
540 Repr::Inline { len, .. } => *len as u8 == 0,
541 }
542 }
543
544 #[inline]
545 fn as_str(&self) -> &str {
546 match self {
547 Repr::Heap(data) => data,
548 Repr::Static(data) => data,
549 Repr::Inline { len, buf } => {
550 let len = *len as usize;
551 let buf = &buf[..len];
552 unsafe { ::core::str::from_utf8_unchecked(buf) }
554 }
555 }
556 }
557
558 fn ptr_eq(&self, other: &Self) -> bool {
559 match (self, other) {
560 (Self::Heap(l0), Self::Heap(r0)) => Arc::ptr_eq(l0, r0),
561 (Self::Static(l0), Self::Static(r0)) => core::ptr::eq(l0, r0),
562 (
563 Self::Inline {
564 len: l_len,
565 buf: l_buf,
566 },
567 Self::Inline {
568 len: r_len,
569 buf: r_buf,
570 },
571 ) => l_len == r_len && l_buf == r_buf,
572 _ => false,
573 }
574 }
575}
576
577pub trait ToSmolStr {
581 fn to_smolstr(&self) -> SmolStr;
582}
583
584pub trait StrExt: private::Sealed {
586 #[must_use = "this returns a new SmolStr without modifying the original"]
591 fn to_lowercase_smolstr(&self) -> SmolStr;
592
593 #[must_use = "this returns a new SmolStr without modifying the original"]
598 fn to_uppercase_smolstr(&self) -> SmolStr;
599
600 #[must_use = "this returns a new SmolStr without modifying the original"]
605 fn to_ascii_lowercase_smolstr(&self) -> SmolStr;
606
607 #[must_use = "this returns a new SmolStr without modifying the original"]
612 fn to_ascii_uppercase_smolstr(&self) -> SmolStr;
613
614 #[must_use = "this returns a new SmolStr without modifying the original"]
620 fn replace_smolstr(&self, from: &str, to: &str) -> SmolStr;
621
622 #[must_use = "this returns a new SmolStr without modifying the original"]
628 fn replacen_smolstr(&self, from: &str, to: &str, count: usize) -> SmolStr;
629}
630
631impl StrExt for str {
632 #[inline]
633 fn to_lowercase_smolstr(&self) -> SmolStr {
634 SmolStr::from_char_iter(self.chars().flat_map(|c| c.to_lowercase()))
635 }
636
637 #[inline]
638 fn to_uppercase_smolstr(&self) -> SmolStr {
639 SmolStr::from_char_iter(self.chars().flat_map(|c| c.to_uppercase()))
640 }
641
642 #[inline]
643 fn to_ascii_lowercase_smolstr(&self) -> SmolStr {
644 SmolStr::from_char_iter(self.chars().map(|c| c.to_ascii_lowercase()))
645 }
646
647 #[inline]
648 fn to_ascii_uppercase_smolstr(&self) -> SmolStr {
649 SmolStr::from_char_iter(self.chars().map(|c| c.to_ascii_uppercase()))
650 }
651
652 #[inline]
653 fn replace_smolstr(&self, from: &str, to: &str) -> SmolStr {
654 self.replacen_smolstr(from, to, usize::MAX)
655 }
656
657 #[inline]
658 fn replacen_smolstr(&self, from: &str, to: &str, count: usize) -> SmolStr {
659 let mut result = Writer::new();
660 let mut last_end = 0;
661 for (start, part) in self.match_indices(from).take(count) {
662 result.push_str(unsafe { self.get_unchecked(last_end..start) });
665 result.push_str(to);
666 last_end = start + part.len();
667 }
668 result.push_str(unsafe { self.get_unchecked(last_end..self.len()) });
671 SmolStr::from(result)
672 }
673}
674
675mod private {
676 pub trait Sealed {}
678 impl Sealed for str {}
679}
680
681#[macro_export]
685macro_rules! format_smolstr {
686 ($($tt:tt)*) => {{
687 use ::core::fmt::Write;
688 let mut w = $crate::Writer::new();
689 w.write_fmt(format_args!($($tt)*)).expect("a formatting trait implementation returned an error");
690 $crate::SmolStr::from(w)
691 }};
692}
693
694#[doc(hidden)]
695pub struct Writer {
696 inline: [u8; INLINE_CAP],
697 heap: String,
698 len: usize,
699}
700
701impl Writer {
702 #[must_use]
703 pub const fn new() -> Self {
704 Writer {
705 inline: [0; INLINE_CAP],
706 heap: String::new(),
707 len: 0,
708 }
709 }
710
711 fn push_str(&mut self, s: &str) {
712 if self.len <= INLINE_CAP {
714 let old_len = self.len;
715 self.len += s.len();
716
717 if self.len <= INLINE_CAP {
719 self.inline[old_len..self.len].copy_from_slice(s.as_bytes());
720 return; }
722
723 self.heap.reserve(self.len);
724
725 unsafe {
728 self.heap
729 .as_mut_vec()
730 .extend_from_slice(&self.inline[..old_len]);
731 }
732 }
733
734 self.heap.push_str(s);
735 }
736}
737
738impl fmt::Write for Writer {
739 #[inline]
740 fn write_str(&mut self, s: &str) -> fmt::Result {
741 self.push_str(s);
742 Ok(())
743 }
744}
745
746impl From<Writer> for SmolStr {
747 fn from(value: Writer) -> Self {
748 SmolStr(if value.len <= INLINE_CAP {
749 Repr::Inline {
750 len: unsafe { InlineSize::transmute_from_u8(value.len as u8) },
752 buf: value.inline,
753 }
754 } else {
755 Repr::new(value.heap)
756 })
757 }
758}
759
760impl<T> ToSmolStr for T
761where
762 T: fmt::Display + ?Sized,
763{
764 fn to_smolstr(&self) -> SmolStr {
765 format_smolstr!("{}", self)
766 }
767}
768
769#[cfg(feature = "serde")]
770mod serde {
771 use alloc::{string::String, vec::Vec};
772 use core::fmt;
773
774 use serde::de::{Deserializer, Error, Unexpected, Visitor};
775
776 use crate::SmolStr;
777
778 fn smol_str<'de: 'a, 'a, D>(deserializer: D) -> Result<SmolStr, D::Error>
780 where
781 D: Deserializer<'de>,
782 {
783 struct SmolStrVisitor;
784
785 impl<'a> Visitor<'a> for SmolStrVisitor {
786 type Value = SmolStr;
787
788 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
789 formatter.write_str("a string")
790 }
791
792 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
793 where
794 E: Error,
795 {
796 Ok(SmolStr::from(v))
797 }
798
799 fn visit_borrowed_str<E>(self, v: &'a str) -> Result<Self::Value, E>
800 where
801 E: Error,
802 {
803 Ok(SmolStr::from(v))
804 }
805
806 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
807 where
808 E: Error,
809 {
810 Ok(SmolStr::from(v))
811 }
812
813 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
814 where
815 E: Error,
816 {
817 match core::str::from_utf8(v) {
818 Ok(s) => Ok(SmolStr::from(s)),
819 Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
820 }
821 }
822
823 fn visit_borrowed_bytes<E>(self, v: &'a [u8]) -> Result<Self::Value, E>
824 where
825 E: Error,
826 {
827 match core::str::from_utf8(v) {
828 Ok(s) => Ok(SmolStr::from(s)),
829 Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
830 }
831 }
832
833 fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
834 where
835 E: Error,
836 {
837 match String::from_utf8(v) {
838 Ok(s) => Ok(SmolStr::from(s)),
839 Err(e) => Err(Error::invalid_value(
840 Unexpected::Bytes(&e.into_bytes()),
841 &self,
842 )),
843 }
844 }
845 }
846
847 deserializer.deserialize_str(SmolStrVisitor)
848 }
849
850 impl serde::Serialize for SmolStr {
851 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
852 where
853 S: serde::Serializer,
854 {
855 self.as_str().serialize(serializer)
856 }
857 }
858
859 impl<'de> serde::Deserialize<'de> for SmolStr {
860 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
861 where
862 D: serde::Deserializer<'de>,
863 {
864 smol_str(deserializer)
865 }
866 }
867}