Unnamed repository; edit this file 'description' to name the repository.
Add some missing smol_str API pieces
Lukas Wirth 7 weeks ago
parent ceeac4a · commit 8ff72bb
-rw-r--r--lib/smol_str/src/borsh.rs5
-rw-r--r--lib/smol_str/src/lib.rs120
-rw-r--r--lib/smol_str/src/serde.rs2
-rw-r--r--lib/smol_str/tests/test.rs24
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;