From 29d2156379297366161c68918aa8456a37e8a9f9 Mon Sep 17 00:00:00 2001 From: Markus Reiter Date: Tue, 23 Jan 2024 02:21:56 +0100 Subject: [PATCH] Add `String::drain`. --- CHANGELOG.md | 1 + src/string/drain.rs | 134 +++++++++++++++++++++++++++++++ src/{string.rs => string/mod.rs} | 69 +++++++++++++++- 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 src/string/drain.rs rename src/{string.rs => string/mod.rs} (91%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34dd577825..523a3ea353 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `Deque::{get, get_mut, get_unchecked, get_unchecked_mut}`. - Added `serde::Serialize` and `serde::Deserialize` implementations to `HistoryBuffer`. - Added `Vec::drain`. +- Added `String::drain`. ### Changed diff --git a/src/string/drain.rs b/src/string/drain.rs new file mode 100644 index 0000000000..c547e5f4b2 --- /dev/null +++ b/src/string/drain.rs @@ -0,0 +1,134 @@ +use core::{fmt, iter::FusedIterator, str::Chars}; + +use super::String; + +/// A draining iterator for `String`. +/// +/// This struct is created by the [`drain`] method on [`String`]. See its +/// documentation for more. +/// +/// [`drain`]: String::drain +pub struct Drain<'a, const N: usize> { + /// Will be used as &'a mut String in the destructor + pub(super) string: *mut String, + /// Start of part to remove + pub(super) start: usize, + /// End of part to remove + pub(super) end: usize, + /// Current remaining range to remove + pub(super) iter: Chars<'a>, +} + +impl fmt::Debug for Drain<'_, N> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Drain").field(&self.as_str()).finish() + } +} + +unsafe impl Sync for Drain<'_, N> {} +unsafe impl Send for Drain<'_, N> {} + +impl Drop for Drain<'_, N> { + fn drop(&mut self) { + unsafe { + // Use `Vec::drain`. “Reaffirm” the bounds checks to avoid + // panic code being inserted again. + let self_vec = (*self.string).as_mut_vec(); + if self.start <= self.end && self.end <= self_vec.len() { + self_vec.drain(self.start..self.end); + } + } + } +} + +impl<'a, const N: usize> Drain<'a, N> { + /// Returns the remaining (sub)string of this iterator as a slice. + /// + /// # Examples + /// + /// ``` + /// use heapless::String; + /// + /// let mut s = String::<8>::try_from("abc").unwrap(); + /// let mut drain = s.drain(..); + /// assert_eq!(drain.as_str(), "abc"); + /// let _ = drain.next().unwrap(); + /// assert_eq!(drain.as_str(), "bc"); + /// ``` + #[must_use] + pub fn as_str(&self) -> &str { + self.iter.as_str() + } +} + +impl AsRef for Drain<'_, N> { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl AsRef<[u8]> for Drain<'_, N> { + fn as_ref(&self) -> &[u8] { + self.as_str().as_bytes() + } +} + +impl Iterator for Drain<'_, N> { + type Item = char; + + #[inline] + fn next(&mut self) -> Option { + self.iter.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + + #[inline] + fn last(mut self) -> Option { + self.next_back() + } +} + +impl DoubleEndedIterator for Drain<'_, N> { + #[inline] + fn next_back(&mut self) -> Option { + self.iter.next_back() + } +} + +impl FusedIterator for Drain<'_, N> {} + +#[cfg(test)] +mod tests { + use super::String; + + #[test] + fn drain_front() { + let mut s = String::<8>::try_from("abcd").unwrap(); + let mut it = s.drain(..1); + assert_eq!(it.next(), Some('a')); + drop(it); + assert_eq!(s, "bcd"); + } + + #[test] + fn drain_middle() { + let mut s = String::<8>::try_from("abcd").unwrap(); + let mut it = s.drain(1..3); + assert_eq!(it.next(), Some('b')); + assert_eq!(it.next(), Some('c')); + drop(it); + assert_eq!(s, "ad"); + } + + #[test] + fn drain_end() { + let mut s = String::<8>::try_from("abcd").unwrap(); + let mut it = s.drain(3..); + assert_eq!(it.next(), Some('d')); + drop(it); + assert_eq!(s, "abc"); + } +} diff --git a/src/string.rs b/src/string/mod.rs similarity index 91% rename from src/string.rs rename to src/string/mod.rs index dce6394d2d..ceeac8455e 100644 --- a/src/string.rs +++ b/src/string/mod.rs @@ -5,12 +5,16 @@ use core::{ cmp::Ordering, fmt, fmt::{Arguments, Write}, - hash, iter, ops, + hash, iter, + ops::{self, Range, RangeBounds}, str::{self, Utf8Error}, }; use crate::Vec; +mod drain; +pub use drain::Drain; + /// A possible error value when converting a [`String`] from a UTF-16 byte slice. /// /// This type is the error type for the [`from_utf16`] method on [`String`]. @@ -456,6 +460,69 @@ impl String { pub fn clear(&mut self) { self.vec.clear() } + + /// Removes the specified range from the string in bulk, returning all + /// removed characters as an iterator. + /// + /// The returned iterator keeps a mutable borrow on the string to optimize + /// its implementation. + /// + /// # Panics + /// + /// Panics if the starting point or end point do not lie on a [`char`] + /// boundary, or if they're out of bounds. + /// + /// # Leaking + /// + /// If the returned iterator goes out of scope without being dropped (due to + /// [`core::mem::forget`], for example), the string may still contain a copy + /// of any drained characters, or may have lost characters arbitrarily, + /// including characters outside the range. + /// + /// # Examples + /// + /// ``` + /// use heapless::String; + /// + /// let mut s = String::<32>::try_from("α is alpha, β is beta").unwrap(); + /// let beta_offset = s.find('β').unwrap_or(s.len()); + /// + /// // Remove the range up until the β from the string + /// let t: String<32> = s.drain(..beta_offset).collect(); + /// assert_eq!(t, "α is alpha, "); + /// assert_eq!(s, "β is beta"); + /// + /// // A full range clears the string, like `clear()` does + /// s.drain(..); + /// assert_eq!(s, ""); + /// ``` + pub fn drain(&mut self, range: R) -> Drain<'_, N> + where + R: RangeBounds, + { + // Memory safety + // + // The `String` version of `Drain` does not have the memory safety issues + // of the `Vec` version. The data is just plain bytes. + // Because the range removal happens in `Drop`, if the `Drain` iterator is leaked, + // the removal will not happen. + let Range { start, end } = crate::slice::range(range, ..self.len()); + assert!(self.is_char_boundary(start)); + assert!(self.is_char_boundary(end)); + + // Take out two simultaneous borrows. The &mut String won't be accessed + // until iteration is over, in Drop. + let self_ptr = self as *mut _; + // SAFETY: `slice::range` and `is_char_boundary` do the appropriate bounds checks. + let chars_iter = unsafe { self.get_unchecked(start..end) }.chars(); + + Drain { + start, + end, + iter: chars_iter, + string: self_ptr, + } + } } impl Default for String {