From d224a7d34615d3e5e21c7f5807d9ee9535a8b311 Mon Sep 17 00:00:00 2001 From: polazarus Date: Wed, 17 Jul 2024 10:52:57 +0200 Subject: [PATCH] feat: add truncate, pop, and clear --- src/bytes.rs | 76 +++++++++++++++++++++++++++++++++++--- src/bytes/tests.rs | 76 ++++++++++++++++++++++++++++++++++++-- src/raw.rs | 40 +++++++++++++++++++- src/raw/allocated.rs | 10 +++++ src/raw/tests.rs | 10 +++++ src/string.rs | 63 ++++++++++++++++++++++++++++++++ src/string/tests.rs | 87 +++++++++++++++++++++++++++++++++++++++----- tests/basic.rs | 17 +++++++++ 8 files changed, 358 insertions(+), 21 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index 5ea9ca8..67f2bab 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -291,11 +291,11 @@ where /// assert_eq!(b"bar", slice); /// ``` #[inline] + #[doc(alias = "make_mut")] pub fn to_mut_slice(&mut self) -> &mut [u8] { self.0.make_unique(); - let slice = self.0.as_mut_slice(); // SAFETY: `make_unique` above ensures that it is uniquely owned - unsafe { slice.unwrap_unchecked() } + unsafe { self.0.as_mut_slice_unchecked() } } /// Extracts a slice as its own `HipByt`. @@ -496,6 +496,66 @@ where } } + /// Shortens this `HipByt` to the specified length. + /// + /// If the new length is greater than the current length, this has no effect. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// # use hipstr::HipByt; + /// let mut a = HipByt::from(b"abc"); + /// a.truncate(1); + /// assert_eq!(a, b"a"); + /// ``` + #[inline] + pub fn truncate(&mut self, new_len: usize) { + self.0.truncate(new_len); + } + + /// Truncates this `HipByt`, removing all contents. + /// + /// # Examples + /// + /// ``` + /// # use hipstr::HipByt; + /// let mut s = HipByt::from(b"foo"); + /// + /// s.clear(); + /// + /// assert!(s.is_empty()); + /// assert_eq!(0, s.len()); + /// ``` + #[inline] + pub fn clear(&mut self) { + self.0.truncate(0); + } + + /// Removes the last element from this `HipByt` and returns it, or [`None`] + /// if it is empty. + /// + /// # Examples + /// + /// ``` + /// # use hipstr::HipByt; + /// + /// let mut h = HipByt::from(&[1, 2, 3]); + /// assert_eq!(h.pop(), Some(3)); + /// assert_eq!(h, [1, 2]); + /// ``` + pub fn pop(&mut self) -> Option { + let len = self.len(); + if len == 0 { + None + } else { + let result = unsafe { *self.as_slice().get_unchecked(len - 1) }; + self.truncate(len - 1); + Some(result) + } + } + /// Appends all bytes of the slice to this `HipByt`. /// /// # Examples @@ -531,6 +591,10 @@ where /// Makes the data owned, copying it if the data is actually borrowed. /// + /// Returns a new `HipByt` consuming this one. + /// + /// # Examples + /// /// ``` /// # use hipstr::HipByt; /// let v = vec![42; 42]; @@ -721,6 +785,7 @@ where /// let c = HipByt::concat_slices(&[b"hello", b" world", b"!"]); /// assert_eq!(c, b"hello world!"); /// ``` + #[must_use] pub fn concat_slices(slices: &[&[u8]]) -> Self { let new_len = slices.iter().map(|e| e.len()).sum(); @@ -774,6 +839,7 @@ where /// let c3 = HipByt::concat(vec![b"hello".as_slice(), b" world", b"!"].iter()); /// assert_eq!(c3, b"hello world!"); /// ``` + #[must_use] pub fn concat(slices: I) -> Self where E: AsRef<[u8]>, @@ -826,6 +892,7 @@ where /// let joined = HipByt::join_slices(slices, sep); /// assert_eq!(joined, b"hello, world, rust"); /// ``` + #[must_use] pub fn join_slices(slices: &[&[u8]], sep: impl AsRef<[u8]>) -> Self { let slices_len = slices.len(); if slices_len == 0 { @@ -849,12 +916,11 @@ where // compute the final pointer let final_ptr = unsafe { dst_ptr.add(new_len) }; - let mut iter = slices.iter(); + let mut iter = slices.iter().copied(); // get first slice // SAFETY: segments > 0 is checked above let slice = unsafe { iter.next().unwrap_unchecked() }; - let slice = slice.as_ref(); let len = slice.len(); // SAFETY: dst_ptr + len cannot overflow let end_ptr = unsafe { dst_ptr.add(len) }; @@ -872,7 +938,6 @@ where } dst_ptr = end_ptr; - let slice = slice.as_ref(); let len = slice.len(); let end_ptr = unsafe { dst_ptr.add(len) }; debug_assert!(end_ptr <= final_ptr, "slices changed during concat"); @@ -918,6 +983,7 @@ where /// let joined = HipByt::join(slices.to_vec().iter(), sep); /// assert_eq!(joined, b"hello, world, rust"); /// ``` + #[must_use] pub fn join(slices: I, sep: impl AsRef<[u8]>) -> Self where E: AsRef<[u8]>, diff --git a/src/bytes/tests.rs b/src/bytes/tests.rs index 6044c8d..e793d2d 100644 --- a/src/bytes/tests.rs +++ b/src/bytes/tests.rs @@ -259,35 +259,49 @@ fn test_as_mut_slice() { } #[test] -fn test_to_mut_slice_static() { +fn test_to_mut_slice_borrowed() { let mut a = H::borrowed(ABC); assert!(a.is_borrowed()); - assert_eq!(a.to_mut_slice(), ABC.to_vec().as_mut_slice()); + assert_eq!(a.to_mut_slice(), ABC); assert!(a.is_inline()); + + let mut a = H::borrowed(MEDIUM); + assert!(a.is_borrowed()); + assert_eq!(a.to_mut_slice(), MEDIUM); + assert!(a.is_allocated()); } #[test] fn test_to_mut_slice_inline() { let mut a = H::from(ABC); + let p = a.as_ptr(); assert!(a.is_inline()); assert_eq!(a.to_mut_slice(), ABC); assert!(a.is_inline()); + assert_eq!(a.as_ptr(), p); } #[test] fn test_to_mut_slice_allocated() { let mut a = H::from(MEDIUM); + let p = a.as_ptr(); assert!(a.is_allocated()); { let sl = a.to_mut_slice(); assert_eq!(sl, MEDIUM); + assert_eq!(sl.as_ptr(), p); sl[0] = 43; } + let mut b = a.clone(); assert_eq!(b[0], 43); - let _ = b.to_mut_slice(); + { + let sl = b.to_mut_slice(); + sl[0] = 42; + } + assert_eq!(b, MEDIUM); assert!(b.is_allocated()); - assert_ne!(b.as_ptr(), a.as_ptr()); + assert_ne!(b.as_ptr(), p); } #[test] @@ -620,6 +634,60 @@ fn test_mutate_allocated() { } } +#[test] +fn test_truncate() { + let mut h = H::borrowed(MEDIUM); + h.truncate(MEDIUM.len() + 1); + assert_eq!(h, MEDIUM); + + let mut h = H::borrowed(MEDIUM); + h.truncate(1); + assert_eq!(h, &MEDIUM[..1]); + + let mut h = H::from(MEDIUM); + h.truncate(1); + assert_eq!(h, &MEDIUM[..1]); + + let mut h = H::from(&MEDIUM[..INLINE_CAPACITY]); + h.truncate(1); + assert_eq!(h, &MEDIUM[..1]); +} + +#[test] +fn test_clear() { + let mut h = H::borrowed(MEDIUM); + h.clear(); + assert!(h.is_empty()); + assert!(!h.is_allocated()); + + let mut h = H::from(MEDIUM); + h.clear(); + assert!(h.is_empty()); + assert!(!h.is_allocated()); + + let mut h = H::from(&MEDIUM[..INLINE_CAPACITY]); + h.clear(); + assert!(h.is_empty()); + assert!(!h.is_allocated()); +} + +#[test] +fn test_pop() { + let mut h = H::borrowed(MEDIUM); + assert_eq!(h.pop(), Some(MEDIUM[0])); + assert_eq!(h, &MEDIUM[..MEDIUM.len() - 1]); + + let mut h = H::from(MEDIUM); + assert_eq!(h.pop(), Some(MEDIUM[0])); + assert_eq!(h, &MEDIUM[..MEDIUM.len() - 1]); + + let mut h = H::from(&MEDIUM[..INLINE_CAPACITY]); + assert_eq!(h.pop(), Some(MEDIUM[0])); + assert_eq!(h, &MEDIUM[..INLINE_CAPACITY - 1]); + + assert_eq!(H::new().pop(), None); +} + #[test] fn test_push_slice_borrowed() { #[track_caller] diff --git a/src/raw.rs b/src/raw.rs index e8a4d50..78c822f 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -290,6 +290,7 @@ impl<'borrow, B: Backend> Raw<'borrow, B> { /// Creates a new `Raw` from a slice. /// /// Will normalize the representation depending on the size of the slice. + #[inline] pub fn from_slice(bytes: &[u8]) -> Self { let len = bytes.len(); if len <= INLINE_CAPACITY { @@ -460,6 +461,7 @@ impl<'borrow, B: Backend> Raw<'borrow, B> { result } + /// Returns a mutable slice if this `Raw` is neither borrowed nor shared. #[inline] pub fn as_mut_slice(&mut self) -> Option<&mut [u8]> { match self.split_mut() { @@ -469,6 +471,26 @@ impl<'borrow, B: Backend> Raw<'borrow, B> { } } + /// Returns a mutable slice of the underlying string. + /// + /// # Safety + /// + /// This `Raw` should not be shared or borrowed. + #[inline] + pub unsafe fn as_mut_slice_unchecked(&mut self) -> &mut [u8] { + match self.split_mut() { + RawSplitMut::Inline(inline) => inline.as_mut_slice(), + RawSplitMut::Allocated(allocated) => unsafe { allocated.as_mut_slice_unchecked() }, + RawSplitMut::Borrowed(_) => { + if cfg!(debug_assertions) { + panic!("mutable slice of borrowed string"); + } else { + unsafe { unreachable_unchecked() } + } + } + } + } + /// Push a slice at the end of this raw byte string. #[inline] pub fn push_slice(&mut self, addition: &[u8]) { @@ -510,8 +532,6 @@ impl<'borrow, B: Backend> Raw<'borrow, B> { // SAFETY: vec's len (new_len) is checked above to be > INLINE_CAPACITY *self = Self::from_vec(vec); - - return; } /// Takes a vector representation of this raw byte string. @@ -748,6 +768,22 @@ impl<'borrow, B: Backend> Raw<'borrow, B> { RawSplitMut::Allocated(allocated) => unsafe { allocated.set_len(new_len) }, } } + + /// Shortens and normalizes the vector keeping the first `new_len` elements. + /// + /// Do nothing is `new_len` is greater than the current length. + pub fn truncate(&mut self, new_len: usize) { + if new_len < self.len() { + if self.is_allocated() && new_len <= INLINE_CAPACITY { + let new = + unsafe { Self::inline_unchecked(self.as_slice().get_unchecked(..new_len)) }; + *self = new; + } else { + unsafe { self.set_len(new_len) } + } + } + debug_assert!(self.is_normalized()); + } } impl<'borrow, B: Backend> Drop for Raw<'borrow, B> { diff --git a/src/raw/allocated.rs b/src/raw/allocated.rs index 5d46bb8..549d30e 100644 --- a/src/raw/allocated.rs +++ b/src/raw/allocated.rs @@ -188,6 +188,16 @@ impl Allocated { } } + /// Returns a mutable slice. + /// + /// # Safety + /// + /// The caller must ensure that the `Allocated` is actually uniquely shared. + #[inline] + pub unsafe fn as_mut_slice_unchecked(&mut self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.ptr.cast_mut(), self.len) } + } + /// Creates a new `Allocated` for some range with the same owner. /// /// # Safety diff --git a/src/raw/tests.rs b/src/raw/tests.rs index f5ee7e3..e07cf0f 100644 --- a/src/raw/tests.rs +++ b/src/raw/tests.rs @@ -26,3 +26,13 @@ fn test_union() { }; let _: R = union.into_raw(); } + +#[cfg(debug_assertions)] +#[should_panic] +#[test] +fn test_to_mut_slice_unchecked_panic() { + let mut r = R::borrowed(b"abc"); + unsafe { + let _sl = r.as_mut_slice_unchecked(); + } +} diff --git a/src/string.rs b/src/string.rs index 2d689ab..b99b45a 100644 --- a/src/string.rs +++ b/src/string.rs @@ -757,6 +757,67 @@ where } } + /// Shortens this string to the specified length. + /// + /// If `new_len` is greater than the string's current length, this has no + /// effect. + /// + /// # Panics + /// + /// Panics if `new_len` is not a char boundary. + /// + /// # Examples + /// ``` + /// # use hipstr::HipStr; + /// let mut s = HipStr::from("abc"); + /// s.truncate(1); + /// assert_eq!(s, "a"); + /// ``` + #[inline] + pub fn truncate(&mut self, new_len: usize) { + if new_len <= self.len() { + assert!(self.is_char_boundary(new_len), "char boundary"); + self.0.truncate(new_len); + } + } + + /// Truncates this `HipStr`, removing all contents. + /// + /// # Examples + /// + /// ``` + /// # use hipstr::HipStr; + /// let mut s = HipStr::from("foo"); + /// + /// s.clear(); + /// + /// assert!(s.is_empty()); + /// assert_eq!(0, s.len()); + /// ``` + #[inline] + pub fn clear(&mut self) { + self.0.clear(); + } + + /// Removes the last character from the string and returns it. + /// + /// Returns `None` if the string is empty. + /// + /// # Examples + /// + /// ``` + /// # use hipstr::HipStr; + /// let mut s = HipStr::from("abc"); + /// assert_eq!(s.pop(), Some('c')); + /// assert_eq!(s, "ab"); + /// ``` + #[inline] + pub fn pop(&mut self) -> Option { + let (i, ch) = self.as_str().char_indices().next_back()?; + self.truncate(i); + Some(ch) + } + /// Appends a given string slice onto the end of this `HipStr`. /// /// # Examples @@ -1431,6 +1492,8 @@ where /// let c = HipByt::concat_slices(&[b"hello", b" world", b"!"]); /// assert_eq!(c, b"hello world!"); /// ``` + #[inline] + #[must_use] pub fn concat_slices(slices: &[&str]) -> Self { let slices: &[&[u8]] = unsafe { transmute(slices) }; diff --git a/src/string/tests.rs b/src/string/tests.rs index 439a28a..1b5e8ea 100644 --- a/src/string/tests.rs +++ b/src/string/tests.rs @@ -615,19 +615,70 @@ fn test_mutate_allocated() { } #[test] -fn test_from_utf16() { - let v = [b'a' as u16].repeat(42); - assert_eq!(H::from_utf16(&v[0..4]).unwrap(), A.repeat(4)); - assert_eq!(H::from_utf16(&v).unwrap(), A.repeat(42)); - assert!(H::from_utf16(&[0xD834]).is_err()); +fn test_truncate() { + let mut h = H::borrowed(MEDIUM); + h.truncate(MEDIUM.len() + 1); + assert_eq!(h, MEDIUM); + + let mut h = H::borrowed(MEDIUM); + h.truncate(1); + assert_eq!(h, &MEDIUM[..1]); + + let mut h = H::from(MEDIUM); + h.truncate(1); + assert!(h.is_inline()); + assert_eq!(h, &MEDIUM[..1]); + + let mut h = H::from(&MEDIUM[..INLINE_CAPACITY]); + h.truncate(1); + assert_eq!(h, &MEDIUM[..1]); } #[test] -fn test_from_utf16_lossy() { - let v = [b'a' as u16].repeat(42); - assert_eq!(H::from_utf16_lossy(&v[0..4]), A.repeat(4)); - assert_eq!(H::from_utf16_lossy(&v), A.repeat(42)); - assert_eq!(H::from_utf16_lossy(&[0xD834]), "\u{FFFD}"); +#[should_panic] +fn test_truncate_char_boundary() { + let mut h = H::borrowed("\u{1F980}"); + h.truncate(1); +} + +#[test] +fn test_clear() { + let mut h = H::borrowed(MEDIUM); + h.clear(); + assert!(h.is_empty()); + assert!(!h.is_allocated()); + + let mut h = H::from(MEDIUM); + h.clear(); + assert!(h.is_empty()); + assert!(!h.is_allocated()); + + let mut h = H::from(&MEDIUM[..INLINE_CAPACITY]); + h.clear(); + assert!(h.is_empty()); + assert!(!h.is_allocated()); +} + +#[test] +fn test_pop() { + let mut h = H::borrowed(MEDIUM); + assert_eq!(h.pop(), Some('*')); + assert_eq!(h, &MEDIUM[..MEDIUM.len() - 1]); + + let mut h = H::from(MEDIUM); + assert_eq!(h.pop(), Some('*')); + assert_eq!(h, &MEDIUM[..MEDIUM.len() - 1]); + + let mut h = H::from(&MEDIUM[..INLINE_CAPACITY]); + assert_eq!(h.pop(), Some('*')); + assert_eq!(h, &MEDIUM[..INLINE_CAPACITY - 1]); + + let mut h = H::from(&MEDIUM[..INLINE_CAPACITY + 1]); + assert_eq!(h.pop(), Some('*')); + assert!(h.is_inline()); + assert_eq!(h, &MEDIUM[..INLINE_CAPACITY]); + + assert_eq!(H::new().pop(), None); } #[test] @@ -1133,3 +1184,19 @@ fn test_into_bytes() { assert_eq!(bytes.len(), 42); assert_eq!(bytes.as_slice(), [b'A'; 42]); } + +#[test] +fn test_from_utf16() { + let v = [b'a' as u16].repeat(42); + assert_eq!(H::from_utf16(&v[0..4]).unwrap(), A.repeat(4)); + assert_eq!(H::from_utf16(&v).unwrap(), A.repeat(42)); + assert!(H::from_utf16(&[0xD834]).is_err()); +} + +#[test] +fn test_from_utf16_lossy() { + let v = [b'a' as u16].repeat(42); + assert_eq!(H::from_utf16_lossy(&v[0..4]), A.repeat(4)); + assert_eq!(H::from_utf16_lossy(&v), A.repeat(42)); + assert_eq!(H::from_utf16_lossy(&[0xD834]), "\u{FFFD}"); +} diff --git a/tests/basic.rs b/tests/basic.rs index 07ee9fe..0591930 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -3,6 +3,23 @@ use std::hint::black_box; use hipstr::HipStr; +#[inline(never)] +pub fn new(slice: &str) -> HipStr<'static> { + HipStr::from(slice) +} + +#[inline(never)] +pub fn new_inline(slice: &str) -> HipStr<'static> { + assert!(slice.len() < HipStr::inline_capacity()); + HipStr::from(slice) +} + +#[test] +fn test_new() { + assert_eq!(new("abc"), new_inline("abc")); + assert_eq!(new(&"a".repeat(100)), "a".repeat(100)); +} + #[test] fn test_eq() { let h = HipStr::from("abc");