Skip to content

Commit

Permalink
Add String::drain.
Browse files Browse the repository at this point in the history
  • Loading branch information
reitermarkus committed Jan 24, 2024
1 parent 4432e3d commit 7650144
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added infallible conversions from arrays to `Vec`.
- Added `Vec::spare_capacity_mut`.
- Added `Vec::drain`.
- Added `String::drain`.

### Changed

Expand Down
134 changes: 134 additions & 0 deletions src/string/drain.rs
Original file line number Diff line number Diff line change
@@ -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<N>,
/// 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<const N: usize> 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<const N: usize> Sync for Drain<'_, N> {}
unsafe impl<const N: usize> Send for Drain<'_, N> {}

impl<const N: usize> 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<const N: usize> AsRef<str> for Drain<'_, N> {
fn as_ref(&self) -> &str {
self.as_str()
}
}

impl<const N: usize> AsRef<[u8]> for Drain<'_, N> {
fn as_ref(&self) -> &[u8] {
self.as_str().as_bytes()
}
}

impl<const N: usize> Iterator for Drain<'_, N> {
type Item = char;

#[inline]
fn next(&mut self) -> Option<char> {
self.iter.next()
}

fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}

#[inline]
fn last(mut self) -> Option<char> {
self.next_back()
}
}

impl<const N: usize> DoubleEndedIterator for Drain<'_, N> {
#[inline]
fn next_back(&mut self) -> Option<char> {
self.iter.next_back()
}
}

impl<const N: usize> 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");
}
}
69 changes: 68 additions & 1 deletion src/string.rs → src/string/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
Expand Down Expand Up @@ -456,6 +460,69 @@ impl<const N: usize> String<N> {
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<R>(&mut self, range: R) -> Drain<'_, N>
where
R: RangeBounds<usize>,
{
// 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<const N: usize> Default for String<N> {
Expand Down

0 comments on commit 7650144

Please sign in to comment.