Unnamed repository; edit this file 'description' to name the repository.
Add some missing smol_str API pieces
| -rw-r--r-- | lib/smol_str/src/borsh.rs | 5 | ||||
| -rw-r--r-- | lib/smol_str/src/lib.rs | 120 | ||||
| -rw-r--r-- | lib/smol_str/src/serde.rs | 2 | ||||
| -rw-r--r-- | lib/smol_str/tests/test.rs | 24 |
4 files changed, 144 insertions, 7 deletions
diff --git a/lib/smol_str/src/borsh.rs b/lib/smol_str/src/borsh.rs index 944bbecbcf..44ae513ed4 100644 --- a/lib/smol_str/src/borsh.rs +++ b/lib/smol_str/src/borsh.rs @@ -29,9 +29,8 @@ impl BorshDeserialize for SmolStr { })) } else { // u8::vec_from_reader always returns Some on success in current implementation - let vec = u8::vec_from_reader(len, reader)?.ok_or_else(|| { - Error::new(ErrorKind::Other, "u8::vec_from_reader unexpectedly returned None") - })?; + let vec = u8::vec_from_reader(len, reader)? + .ok_or_else(|| Error::other("u8::vec_from_reader unexpectedly returned None"))?; Ok(SmolStr::from(String::from_utf8(vec).map_err(|err| { let msg = err.to_string(); Error::new(ErrorKind::InvalidData, msg) diff --git a/lib/smol_str/src/lib.rs b/lib/smol_str/src/lib.rs index b30eefc098..55ede286c2 100644 --- a/lib/smol_str/src/lib.rs +++ b/lib/smol_str/src/lib.rs @@ -34,13 +34,17 @@ use core::{ pub struct SmolStr(Repr); impl SmolStr { + /// The maximum byte length of a string that can be stored inline + /// without heap allocation. + pub const INLINE_CAP: usize = INLINE_CAP; + /// Constructs an inline variant of `SmolStr`. /// /// This never allocates. /// /// # Panics /// - /// Panics if `text.len() > 23`. + /// Panics if `text.len() > `[`SmolStr::INLINE_CAP`]. #[inline] pub const fn new_inline(text: &str) -> SmolStr { assert!(text.len() <= INLINE_CAP); // avoids bounds checks in loop @@ -241,6 +245,48 @@ impl PartialOrd for SmolStr { } } +impl PartialOrd<str> for SmolStr { + fn partial_cmp(&self, other: &str) -> Option<Ordering> { + Some(self.as_str().cmp(other)) + } +} + +impl<'a> PartialOrd<&'a str> for SmolStr { + fn partial_cmp(&self, other: &&'a str) -> Option<Ordering> { + Some(self.as_str().cmp(*other)) + } +} + +impl PartialOrd<SmolStr> for &str { + fn partial_cmp(&self, other: &SmolStr) -> Option<Ordering> { + Some((*self).cmp(other.as_str())) + } +} + +impl PartialOrd<String> for SmolStr { + fn partial_cmp(&self, other: &String) -> Option<Ordering> { + Some(self.as_str().cmp(other.as_str())) + } +} + +impl PartialOrd<SmolStr> for String { + fn partial_cmp(&self, other: &SmolStr) -> Option<Ordering> { + Some(self.as_str().cmp(other.as_str())) + } +} + +impl<'a> PartialOrd<&'a String> for SmolStr { + fn partial_cmp(&self, other: &&'a String) -> Option<Ordering> { + Some(self.as_str().cmp(other.as_str())) + } +} + +impl PartialOrd<SmolStr> for &String { + fn partial_cmp(&self, other: &SmolStr) -> Option<Ordering> { + Some(self.as_str().cmp(other.as_str())) + } +} + impl hash::Hash for SmolStr { fn hash<H: hash::Hasher>(&self, hasher: &mut H) { self.as_str().hash(hasher); @@ -385,6 +431,20 @@ impl AsRef<std::path::Path> for SmolStr { } } +impl From<char> for SmolStr { + #[inline] + fn from(c: char) -> SmolStr { + let mut buf = [0; INLINE_CAP]; + let len = c.len_utf8(); + c.encode_utf8(&mut buf); + SmolStr(Repr::Inline { + // SAFETY: A char is at most 4 bytes, which is always <= INLINE_CAP (23). + len: unsafe { InlineSize::transmute_from_u8(len as u8) }, + buf, + }) + } +} + impl From<&str> for SmolStr { #[inline] fn from(s: &str) -> SmolStr { @@ -888,10 +948,18 @@ macro_rules! format_smolstr { /// A builder that can be used to efficiently build a [`SmolStr`]. /// /// This won't allocate if the final string fits into the inline buffer. -#[derive(Clone, Default, Debug, PartialEq, Eq)] +#[derive(Clone, Default, Debug)] pub struct SmolStrBuilder(SmolStrBuilderRepr); -#[derive(Clone, Debug, PartialEq, Eq)] +impl PartialEq for SmolStrBuilder { + fn eq(&self, other: &Self) -> bool { + self.as_str() == other.as_str() + } +} + +impl Eq for SmolStrBuilder {} + +#[derive(Clone, Debug)] enum SmolStrBuilderRepr { Inline { len: usize, buf: [u8; INLINE_CAP] }, Heap(String), @@ -911,6 +979,52 @@ impl SmolStrBuilder { Self(SmolStrBuilderRepr::Inline { buf: [0; INLINE_CAP], len: 0 }) } + /// Creates a new empty [`SmolStrBuilder`] with at least the specified capacity. + /// + /// If `capacity` is less than or equal to [`SmolStr::INLINE_CAP`], the builder + /// will use inline storage and not allocate. Otherwise, it will pre-allocate a + /// heap buffer of the requested capacity. + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + if capacity <= INLINE_CAP { + Self::new() + } else { + Self(SmolStrBuilderRepr::Heap(String::with_capacity(capacity))) + } + } + + /// Returns the number of bytes accumulated in the builder so far. + #[inline] + pub fn len(&self) -> usize { + match &self.0 { + SmolStrBuilderRepr::Inline { len, .. } => *len, + SmolStrBuilderRepr::Heap(heap) => heap.len(), + } + } + + /// Returns `true` if the builder has a length of zero bytes. + #[inline] + pub fn is_empty(&self) -> bool { + match &self.0 { + SmolStrBuilderRepr::Inline { len, .. } => *len == 0, + SmolStrBuilderRepr::Heap(heap) => heap.is_empty(), + } + } + + /// Returns a `&str` slice of the builder's current contents. + #[inline] + pub fn as_str(&self) -> &str { + match &self.0 { + SmolStrBuilderRepr::Inline { len, buf } => { + // SAFETY: `buf[..*len]` was built by prior `push`/`push_str` calls + // that only wrote valid UTF-8, and `*len <= INLINE_CAP` is maintained + // by the inline branch logic. + unsafe { core::str::from_utf8_unchecked(&buf[..*len]) } + } + SmolStrBuilderRepr::Heap(heap) => heap.as_str(), + } + } + /// Builds a [`SmolStr`] from `self`. #[must_use] pub fn finish(self) -> SmolStr { diff --git a/lib/smol_str/src/serde.rs b/lib/smol_str/src/serde.rs index 66cbcd3bad..9d82d64805 100644 --- a/lib/smol_str/src/serde.rs +++ b/lib/smol_str/src/serde.rs @@ -16,7 +16,7 @@ where impl<'a> Visitor<'a> for SmolStrVisitor { type Value = SmolStr; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a string") } diff --git a/lib/smol_str/tests/test.rs b/lib/smol_str/tests/test.rs index 00fab2ee1c..83648edeec 100644 --- a/lib/smol_str/tests/test.rs +++ b/lib/smol_str/tests/test.rs @@ -10,6 +10,7 @@ use smol_str::{SmolStr, SmolStrBuilder}; #[cfg(target_pointer_width = "64")] fn smol_str_is_smol() { assert_eq!(::std::mem::size_of::<SmolStr>(), ::std::mem::size_of::<String>(),); + assert_eq!(::std::mem::size_of::<Option<SmolStr>>(), ::std::mem::size_of::<SmolStr>(),); } #[test] @@ -332,6 +333,29 @@ fn test_builder_push() { assert_eq!("a".repeat(24), s); } +#[test] +fn test_from_char() { + // ASCII char + let s: SmolStr = 'a'.into(); + assert_eq!(s, "a"); + assert!(!s.is_heap_allocated()); + + // Multi-byte char (2 bytes) + let s: SmolStr = SmolStr::from('ñ'); + assert_eq!(s, "ñ"); + assert!(!s.is_heap_allocated()); + + // 3-byte char + let s: SmolStr = '€'.into(); + assert_eq!(s, "€"); + assert!(!s.is_heap_allocated()); + + // 4-byte char (emoji) + let s: SmolStr = '🦀'.into(); + assert_eq!(s, "🦀"); + assert!(!s.is_heap_allocated()); +} + #[cfg(test)] mod test_str_ext { use smol_str::StrExt; |