Skip to content

Commit 39c379c

Browse files
authored
Merge pull request #444 from reitermarkus/drain
Add `Vec::drain` and `String::drain`.
2 parents 310c09d + 29d2156 commit 39c379c

File tree

7 files changed

+576
-2
lines changed

7 files changed

+576
-2
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2121
- Added `IntoIterator` implementation for `LinearMap`
2222
- Added `Deque::{get, get_mut, get_unchecked, get_unchecked_mut}`.
2323
- Added `serde::Serialize` and `serde::Deserialize` implementations to `HistoryBuffer`.
24+
- Added `Vec::drain`.
25+
- Added `String::drain`.
2426

2527
### Changed
2628

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ pub use indexmap::{
9696
pub use indexset::{FnvIndexSet, IndexSet, Iter as IndexSetIter};
9797
pub use linear_map::LinearMap;
9898
pub use string::String;
99+
99100
pub use vec::{Vec, VecView};
100101

101102
#[macro_use]
@@ -107,6 +108,7 @@ mod histbuf;
107108
mod indexmap;
108109
mod indexset;
109110
mod linear_map;
111+
mod slice;
110112
pub mod storage;
111113
pub mod string;
112114
pub mod vec;

src/slice.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use core::ops;
2+
3+
// FIXME: Remove when `slice_range` feature is stable.
4+
#[track_caller]
5+
#[must_use]
6+
pub fn range<R>(range: R, bounds: ops::RangeTo<usize>) -> ops::Range<usize>
7+
where
8+
R: ops::RangeBounds<usize>,
9+
{
10+
let len = bounds.end;
11+
12+
let start: ops::Bound<&usize> = range.start_bound();
13+
let start = match start {
14+
ops::Bound::Included(&start) => start,
15+
ops::Bound::Excluded(start) => start
16+
.checked_add(1)
17+
.unwrap_or_else(|| panic!("attempted to index slice from after maximum usize")),
18+
ops::Bound::Unbounded => 0,
19+
};
20+
21+
let end: ops::Bound<&usize> = range.end_bound();
22+
let end = match end {
23+
ops::Bound::Included(end) => end
24+
.checked_add(1)
25+
.unwrap_or_else(|| panic!("attempted to index slice up to maximum usize")),
26+
ops::Bound::Excluded(&end) => end,
27+
ops::Bound::Unbounded => len,
28+
};
29+
30+
if start > end {
31+
panic!("slice index starts at {start} but ends at {end}");
32+
}
33+
if end > len {
34+
panic!("range end index {end} out of range for slice of length {len}");
35+
}
36+
37+
ops::Range { start, end }
38+
}

src/string/drain.rs

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use core::{fmt, iter::FusedIterator, str::Chars};
2+
3+
use super::String;
4+
5+
/// A draining iterator for `String`.
6+
///
7+
/// This struct is created by the [`drain`] method on [`String`]. See its
8+
/// documentation for more.
9+
///
10+
/// [`drain`]: String::drain
11+
pub struct Drain<'a, const N: usize> {
12+
/// Will be used as &'a mut String in the destructor
13+
pub(super) string: *mut String<N>,
14+
/// Start of part to remove
15+
pub(super) start: usize,
16+
/// End of part to remove
17+
pub(super) end: usize,
18+
/// Current remaining range to remove
19+
pub(super) iter: Chars<'a>,
20+
}
21+
22+
impl<const N: usize> fmt::Debug for Drain<'_, N> {
23+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24+
f.debug_tuple("Drain").field(&self.as_str()).finish()
25+
}
26+
}
27+
28+
unsafe impl<const N: usize> Sync for Drain<'_, N> {}
29+
unsafe impl<const N: usize> Send for Drain<'_, N> {}
30+
31+
impl<const N: usize> Drop for Drain<'_, N> {
32+
fn drop(&mut self) {
33+
unsafe {
34+
// Use `Vec::drain`. “Reaffirm” the bounds checks to avoid
35+
// panic code being inserted again.
36+
let self_vec = (*self.string).as_mut_vec();
37+
if self.start <= self.end && self.end <= self_vec.len() {
38+
self_vec.drain(self.start..self.end);
39+
}
40+
}
41+
}
42+
}
43+
44+
impl<'a, const N: usize> Drain<'a, N> {
45+
/// Returns the remaining (sub)string of this iterator as a slice.
46+
///
47+
/// # Examples
48+
///
49+
/// ```
50+
/// use heapless::String;
51+
///
52+
/// let mut s = String::<8>::try_from("abc").unwrap();
53+
/// let mut drain = s.drain(..);
54+
/// assert_eq!(drain.as_str(), "abc");
55+
/// let _ = drain.next().unwrap();
56+
/// assert_eq!(drain.as_str(), "bc");
57+
/// ```
58+
#[must_use]
59+
pub fn as_str(&self) -> &str {
60+
self.iter.as_str()
61+
}
62+
}
63+
64+
impl<const N: usize> AsRef<str> for Drain<'_, N> {
65+
fn as_ref(&self) -> &str {
66+
self.as_str()
67+
}
68+
}
69+
70+
impl<const N: usize> AsRef<[u8]> for Drain<'_, N> {
71+
fn as_ref(&self) -> &[u8] {
72+
self.as_str().as_bytes()
73+
}
74+
}
75+
76+
impl<const N: usize> Iterator for Drain<'_, N> {
77+
type Item = char;
78+
79+
#[inline]
80+
fn next(&mut self) -> Option<char> {
81+
self.iter.next()
82+
}
83+
84+
fn size_hint(&self) -> (usize, Option<usize>) {
85+
self.iter.size_hint()
86+
}
87+
88+
#[inline]
89+
fn last(mut self) -> Option<char> {
90+
self.next_back()
91+
}
92+
}
93+
94+
impl<const N: usize> DoubleEndedIterator for Drain<'_, N> {
95+
#[inline]
96+
fn next_back(&mut self) -> Option<char> {
97+
self.iter.next_back()
98+
}
99+
}
100+
101+
impl<const N: usize> FusedIterator for Drain<'_, N> {}
102+
103+
#[cfg(test)]
104+
mod tests {
105+
use super::String;
106+
107+
#[test]
108+
fn drain_front() {
109+
let mut s = String::<8>::try_from("abcd").unwrap();
110+
let mut it = s.drain(..1);
111+
assert_eq!(it.next(), Some('a'));
112+
drop(it);
113+
assert_eq!(s, "bcd");
114+
}
115+
116+
#[test]
117+
fn drain_middle() {
118+
let mut s = String::<8>::try_from("abcd").unwrap();
119+
let mut it = s.drain(1..3);
120+
assert_eq!(it.next(), Some('b'));
121+
assert_eq!(it.next(), Some('c'));
122+
drop(it);
123+
assert_eq!(s, "ad");
124+
}
125+
126+
#[test]
127+
fn drain_end() {
128+
let mut s = String::<8>::try_from("abcd").unwrap();
129+
let mut it = s.drain(3..);
130+
assert_eq!(it.next(), Some('d'));
131+
drop(it);
132+
assert_eq!(s, "abc");
133+
}
134+
}

src/string.rs src/string/mod.rs

+68-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ use core::{
55
cmp::Ordering,
66
fmt,
77
fmt::{Arguments, Write},
8-
hash, iter, ops,
8+
hash, iter,
9+
ops::{self, Range, RangeBounds},
910
str::{self, Utf8Error},
1011
};
1112

1213
use crate::Vec;
1314

15+
mod drain;
16+
pub use drain::Drain;
17+
1418
/// A possible error value when converting a [`String`] from a UTF-16 byte slice.
1519
///
1620
/// This type is the error type for the [`from_utf16`] method on [`String`].
@@ -456,6 +460,69 @@ impl<const N: usize> String<N> {
456460
pub fn clear(&mut self) {
457461
self.vec.clear()
458462
}
463+
464+
/// Removes the specified range from the string in bulk, returning all
465+
/// removed characters as an iterator.
466+
///
467+
/// The returned iterator keeps a mutable borrow on the string to optimize
468+
/// its implementation.
469+
///
470+
/// # Panics
471+
///
472+
/// Panics if the starting point or end point do not lie on a [`char`]
473+
/// boundary, or if they're out of bounds.
474+
///
475+
/// # Leaking
476+
///
477+
/// If the returned iterator goes out of scope without being dropped (due to
478+
/// [`core::mem::forget`], for example), the string may still contain a copy
479+
/// of any drained characters, or may have lost characters arbitrarily,
480+
/// including characters outside the range.
481+
///
482+
/// # Examples
483+
///
484+
/// ```
485+
/// use heapless::String;
486+
///
487+
/// let mut s = String::<32>::try_from("α is alpha, β is beta").unwrap();
488+
/// let beta_offset = s.find('β').unwrap_or(s.len());
489+
///
490+
/// // Remove the range up until the β from the string
491+
/// let t: String<32> = s.drain(..beta_offset).collect();
492+
/// assert_eq!(t, "α is alpha, ");
493+
/// assert_eq!(s, "β is beta");
494+
///
495+
/// // A full range clears the string, like `clear()` does
496+
/// s.drain(..);
497+
/// assert_eq!(s, "");
498+
/// ```
499+
pub fn drain<R>(&mut self, range: R) -> Drain<'_, N>
500+
where
501+
R: RangeBounds<usize>,
502+
{
503+
// Memory safety
504+
//
505+
// The `String` version of `Drain` does not have the memory safety issues
506+
// of the `Vec` version. The data is just plain bytes.
507+
// Because the range removal happens in `Drop`, if the `Drain` iterator is leaked,
508+
// the removal will not happen.
509+
let Range { start, end } = crate::slice::range(range, ..self.len());
510+
assert!(self.is_char_boundary(start));
511+
assert!(self.is_char_boundary(end));
512+
513+
// Take out two simultaneous borrows. The &mut String won't be accessed
514+
// until iteration is over, in Drop.
515+
let self_ptr = self as *mut _;
516+
// SAFETY: `slice::range` and `is_char_boundary` do the appropriate bounds checks.
517+
let chars_iter = unsafe { self.get_unchecked(start..end) }.chars();
518+
519+
Drain {
520+
start,
521+
end,
522+
iter: chars_iter,
523+
string: self_ptr,
524+
}
525+
}
459526
}
460527

461528
impl<const N: usize> Default for String<N> {

0 commit comments

Comments
 (0)