ShString now uses the StackString from the public API

main
Pantonshire 3 years ago
parent 85de7505e5
commit 93a8cefd06

@ -8,7 +8,7 @@ use std::{
str::FromStr, str::FromStr,
}; };
use buf::{StackString, HeapString}; use super::StackString;
/// A non-growable string where strings 22 bytes or shorter are stored on the stack and longer /// A non-growable string where strings 22 bytes or shorter are stored on the stack and longer
/// strings are stored on the heap. /// strings are stored on the heap.
@ -42,31 +42,14 @@ impl<const N: usize> ShString<N> {
/// or creating a new heap allocation otherwise. /// or creating a new heap allocation otherwise.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn new_from_str(s: &str) -> Self { pub fn new<S>(s: S) -> Self
match StackString::from_str(s) { where
Some(stack_buf) => Self(Repr::Stack(stack_buf)), S: AsRef<str>,
None => Self(Repr::Heap(HeapString::from_str(s))), Box<str>: From<S>,
} {
} match StackString::new(&s) {
Ok(stack_buf) => Self(Repr::Stack(stack_buf)),
/// Creates a new `ShString` from the given owned `String`, moving the string data onto the Err(_) => Self(Repr::Heap(Box::<str>::from(s))),
/// stack if possible or reusing the `String`'s heap allocation otherwise.
#[inline]
#[must_use]
pub fn new_from_string(s: String) -> Self {
match StackString::from_str(&s) {
Some(stack_buf) => Self(Repr::Stack(stack_buf)),
None => Self(Repr::Heap(HeapString::from_string(s))),
}
}
/// Creates a new `ShString` from the given `Cow<str>`.
#[inline]
#[must_use]
pub fn new_from_cow_str(s: Cow<str>) -> Self {
match s {
Cow::Borrowed(s) => Self::new_from_str(s),
Cow::Owned(s) => Self::new_from_string(s),
} }
} }
@ -75,8 +58,8 @@ impl<const N: usize> ShString<N> {
#[must_use] #[must_use]
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
match self { match self {
Self(Repr::Stack(buf)) => buf.as_str(), Self(Repr::Stack(buf)) => buf,
Self(Repr::Heap(buf)) => buf.as_str(), Self(Repr::Heap(buf)) => buf,
} }
} }
@ -85,8 +68,8 @@ impl<const N: usize> ShString<N> {
#[must_use] #[must_use]
pub fn as_str_mut(&mut self) -> &mut str { pub fn as_str_mut(&mut self) -> &mut str {
match self { match self {
Self(Repr::Stack(buf)) => buf.as_str_mut(), Self(Repr::Stack(buf)) => buf,
Self(Repr::Heap(buf)) => buf.as_str_mut(), Self(Repr::Heap(buf)) => buf,
} }
} }
@ -104,7 +87,7 @@ impl<const N: usize> ShString<N> {
/// ///
/// ``` /// ```
/// # use libshire::strings::ShString; /// # use libshire::strings::ShString;
/// let s = ShString::<22>::new_from_str("こんにちは"); /// let s = ShString::<22>::new("こんにちは");
/// assert_eq!(s.len(), 15); /// assert_eq!(s.len(), 15);
/// ``` /// ```
#[inline] #[inline]
@ -120,10 +103,10 @@ impl<const N: usize> ShString<N> {
/// ///
/// ``` /// ```
/// # use libshire::strings::ShString; /// # use libshire::strings::ShString;
/// let s1 = ShString::<22>::new_from_str(""); /// let s1 = ShString::<22>::new("");
/// assert!(s1.is_empty()); /// assert!(s1.is_empty());
/// ///
/// let s2 = ShString::<22>::new_from_str("Hello"); /// let s2 = ShString::<22>::new("Hello");
/// assert!(!s2.is_empty()); /// assert!(!s2.is_empty());
/// ``` /// ```
#[inline] #[inline]
@ -139,10 +122,10 @@ impl<const N: usize> ShString<N> {
/// ///
/// ``` /// ```
/// # use libshire::strings::ShString; /// # use libshire::strings::ShString;
/// let s1 = ShString::<22>::new_from_str("This string's 22 bytes"); /// let s1 = ShString::<22>::new("This string's 22 bytes");
/// assert!(!s1.heap_allocated()); /// assert!(!s1.heap_allocated());
/// ///
/// let s2 = ShString::<22>::new_from_str("This string is 23 bytes"); /// let s2 = ShString::<22>::new("This string is 23 bytes");
/// assert!(s2.heap_allocated()); /// assert!(s2.heap_allocated());
/// ``` /// ```
#[inline] #[inline]
@ -209,21 +192,21 @@ impl<const N: usize> borrow::BorrowMut<str> for ShString<N> {
impl<'a, const N: usize> From<&'a str> for ShString<N> { impl<'a, const N: usize> From<&'a str> for ShString<N> {
#[inline] #[inline]
fn from(s: &'a str) -> Self { fn from(s: &'a str) -> Self {
Self::new_from_str(s) Self::new(s)
} }
} }
impl<const N: usize> From<String> for ShString<N> { impl<const N: usize> From<String> for ShString<N> {
#[inline] #[inline]
fn from(s: String) -> Self { fn from(s: String) -> Self {
Self::new_from_string(s) Self::new(s)
} }
} }
impl<'a, const N: usize> From<Cow<'a, str>> for ShString<N> { impl<'a, const N: usize> From<Cow<'a, str>> for ShString<N> {
#[inline] #[inline]
fn from(s: Cow<'a, str>) -> Self { fn from(s: Cow<'a, str>) -> Self {
Self::new_from_cow_str(s) Self::new(s)
} }
} }
@ -269,7 +252,7 @@ impl<const N: usize> FromStr for ShString<N> {
#[inline] #[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::new_from_str(s)) Ok(Self::new(s))
} }
} }
@ -304,154 +287,14 @@ impl<'de, const N: usize> serde::Deserialize<'de> for ShString<N> {
D: serde::Deserializer<'de> D: serde::Deserializer<'de>
{ {
serde::Deserialize::deserialize(deserializer) serde::Deserialize::deserialize(deserializer)
.map(Self::new_from_str) .map(Self::new::<&'de str>)
} }
} }
#[derive(Clone)] #[derive(Clone)]
enum Repr<const N: usize> { enum Repr<const N: usize> {
Stack(StackString<N>), Stack(StackString<N>),
Heap(HeapString), Heap(Box<str>),
}
mod buf {
use std::str;
/// A stack-allocated string with a capacity of `N` bytes. `len` must be less than or equal to
/// `N`, and the first `len` bytes of `buf` must be valid UTF-8.
#[derive(Clone)]
pub(super) struct StackString<const N: usize> {
buf: [u8; N],
len: u8,
}
impl<const N: usize> StackString<N> {
const MAX_LEN: u8 = {
#[allow(clippy::cast_possible_truncation, clippy::checked_conversions)]
if N <= u8::MAX as usize {
N as u8
} else {
panic!("`N` must be within the bounds of `u8`")
}
};
/// Creates a new `StackString` from a given buffer and length.
///
/// # Safety
///
/// The first `len` bytes of `buf` (i.e. `buf[..len]`) must be valid UTF-8.
#[inline]
pub(super) const unsafe fn from_raw_parts(buf: [u8; N], len: u8) -> Self {
Self { buf, len }
}
#[inline]
pub(super) const fn empty() -> Self {
// SAFETY:
// The first zero bytes of the buffer are valid UTF-8, because an empty byte slice is
// valid UTF-8.
unsafe { Self::from_raw_parts([0; N], 0) }
}
pub(super) fn from_str(s: &str) -> Option<Self> {
let s = s.as_bytes();
// If the length of the string is greater than `Self::MAX_LEN`, it will not fit in the
// stack buffer so return `None`.
let len = u8::try_from(s.len()).ok()?;
if len > Self::MAX_LEN {
return None;
}
let mut buf = [0; N];
buf[..usize::from(len)].copy_from_slice(s);
// SAFETY:
// The first `len` bytes of the buffer are valid UTF-8 because the first `len` bytes of
// the buffer contain data copied from a `&str`, and `&str` is always valid UTF-8.
unsafe { Some(Self::from_raw_parts(buf, len)) }
}
pub(super) fn as_str(&self) -> &str {
// SAFETY:
// `len` being less than or equal to `N` is an invariant of `StackString`, so it is
// always within the bounds of `buf`.
let slice = unsafe { self.buf.get_unchecked(..usize::from(self.len)) };
// SAFETY:
// The first `len` bytes of `buf` being valid UTF-8 is an invariant of `StackString`.
unsafe { str::from_utf8_unchecked(slice) }
}
pub(super) fn as_str_mut(&mut self) -> &mut str {
// SAFETY:
// `len` being less than or equal to `N` is an invariant of `StackString`, so it is
// always within the bounds of `buf`.
let slice = unsafe { self.buf.get_unchecked_mut(..usize::from(self.len)) };
// SAFETY:
// The first `len` bytes of `buf` being valid UTF-8 is an invariant of `StackString`.
unsafe { str::from_utf8_unchecked_mut(slice) }
}
pub(super) fn into_string(self) -> String {
self.as_str().to_owned()
}
pub(super) fn len(&self) -> usize {
usize::from(self.len)
}
pub(super) fn is_empty(&self) -> bool {
self.len == 0
}
}
/// A heap-allocated non-growable string. `buf` must be valid UTF-8.
#[derive(Clone)]
pub(super) struct HeapString {
buf: Box<[u8]>,
}
impl HeapString {
pub(super) fn from_str(s: &str) -> Self {
Self {
buf: s.as_bytes().into(),
}
}
pub(super) fn from_string(s: String) -> Self {
Self {
buf: s.into_boxed_str().into_boxed_bytes(),
}
}
pub(super) fn as_str(&self) -> &str {
// SAFETY:
// `buf` being valid UTF-8 is an invariant of `HeapString`.
unsafe { str::from_utf8_unchecked(&self.buf) }
}
pub(super) fn as_str_mut(&mut self) -> &mut str {
// SAFETY:
// `buf` being valid UTF-8 is an invariant of `HeapString`.
unsafe { str::from_utf8_unchecked_mut(&mut self.buf) }
}
pub(super) fn into_string(self) -> String {
// SAFETY:
// `buf` being valid UTF-8 is an invariant of `HeapString`.
unsafe { String::from_utf8_unchecked(self.buf.into_vec()) }
}
pub(super) fn len(&self) -> usize {
self.buf.len()
}
pub(super) fn is_empty(&self) -> bool {
self.buf.is_empty()
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -476,53 +319,53 @@ mod tests {
let borrowed = Cow::Borrowed(s); let borrowed = Cow::Borrowed(s);
let owned = Cow::<'static, str>::Owned(buf.clone()); let owned = Cow::<'static, str>::Owned(buf.clone());
assert_eq!(ShString22::new_from_str(s).as_str(), s); assert_eq!(ShString22::new(s).as_str(), s);
assert_eq!(ShString22::new_from_string(buf).as_str(), s); assert_eq!(ShString22::new(buf).as_str(), s);
assert_eq!(ShString22::new_from_cow_str(borrowed).as_str(), s); assert_eq!(ShString22::new(borrowed).as_str(), s);
assert_eq!(ShString22::new_from_cow_str(owned).as_str(), s); assert_eq!(ShString22::new(owned).as_str(), s);
} }
} }
#[test] #[test]
fn test_as_str_mut() { fn test_as_str_mut() {
let mut s1 = ShString22::new_from_str("hello"); let mut s1 = ShString22::new("hello");
s1.as_str_mut().make_ascii_uppercase(); s1.as_str_mut().make_ascii_uppercase();
assert_eq!(s1.as_str(), "HELLO"); assert_eq!(s1.as_str(), "HELLO");
let mut s2 = ShString22::new_from_str("the quick brown fox jumps over the lazy dog"); let mut s2 = ShString22::new("the quick brown fox jumps over the lazy dog");
s2.as_str_mut().make_ascii_uppercase(); s2.as_str_mut().make_ascii_uppercase();
assert_eq!(s2.as_str(), "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"); assert_eq!(s2.as_str(), "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG");
} }
#[test] #[test]
fn test_len() { fn test_len() {
assert_eq!(ShString22::new_from_str("").len(), 0); assert_eq!(ShString22::new("").len(), 0);
assert_eq!(ShString22::new_from_str("Hello").len(), 5); assert_eq!(ShString22::new("Hello").len(), 5);
assert_eq!(ShString22::new_from_str("Somethingfortheweekend").len(), 22); assert_eq!(ShString22::new("Somethingfortheweekend").len(), 22);
assert_eq!(ShString22::new_from_str("Dichlorodifluoromethane").len(), 23); assert_eq!(ShString22::new("Dichlorodifluoromethane").len(), 23);
assert_eq!(ShString22::new_from_str("こんにちは").len(), 15); assert_eq!(ShString22::new("こんにちは").len(), 15);
assert_eq!(ShString22::new_from_str("❤️🧡💛💚💙💜").len(), 26); assert_eq!(ShString22::new("❤️🧡💛💚💙💜").len(), 26);
} }
#[test] #[test]
fn test_heap_allocated() { fn test_heap_allocated() {
assert!(!ShString22::new_from_str("").heap_allocated()); assert!(!ShString22::new("").heap_allocated());
assert!(!ShString22::new_from_str("Hello").heap_allocated()); assert!(!ShString22::new("Hello").heap_allocated());
assert!(!ShString22::new_from_str("Somethingfortheweekend").heap_allocated()); assert!(!ShString22::new("Somethingfortheweekend").heap_allocated());
assert!(!ShString22::new_from_str("こんにちは").heap_allocated()); assert!(!ShString22::new("こんにちは").heap_allocated());
assert!(ShString22::new_from_str("Dichlorodifluoromethane").heap_allocated()); assert!(ShString22::new("Dichlorodifluoromethane").heap_allocated());
assert!(ShString22::new_from_str("Squishedbuginsidethescreen").heap_allocated()); assert!(ShString22::new("Squishedbuginsidethescreen").heap_allocated());
assert!(ShString22::new_from_str("❤️🧡💛💚💙💜").heap_allocated()); assert!(ShString22::new("❤️🧡💛💚💙💜").heap_allocated());
} }
#[test] #[test]
fn test_zero_capacity() { fn test_zero_capacity() {
assert_eq!(ShString::<0>::new_from_str("").as_str(), ""); assert_eq!(ShString::<0>::new("").as_str(), "");
assert!(!ShString::<0>::new_from_str("").heap_allocated()); assert!(!ShString::<0>::new("").heap_allocated());
assert_eq!(ShString::<0>::new_from_str("a").as_str(), "a"); assert_eq!(ShString::<0>::new("a").as_str(), "a");
assert!(ShString::<0>::new_from_str("a").heap_allocated()); assert!(ShString::<0>::new("a").heap_allocated());
assert_eq!(ShString::<0>::new_from_str("Hello").as_str(), "Hello"); assert_eq!(ShString::<0>::new("Hello").as_str(), "Hello");
assert!(ShString::<0>::new_from_str("Hello").heap_allocated()); assert!(ShString::<0>::new("Hello").heap_allocated());
} }
} }

Loading…
Cancel
Save