Skip to content

Commit

Permalink
Add Vec::drain.
Browse files Browse the repository at this point in the history
  • Loading branch information
reitermarkus authored and Dirbaio committed Jun 30, 2024
1 parent 310c09d commit 321c4c7
Show file tree
Hide file tree
Showing 5 changed files with 373 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `IntoIterator` implementation for `LinearMap`
- Added `Deque::{get, get_mut, get_unchecked, get_unchecked_mut}`.
- Added `serde::Serialize` and `serde::Deserialize` implementations to `HistoryBuffer`.
- Added `Vec::drain`.

### Changed

Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub use indexmap::{
pub use indexset::{FnvIndexSet, IndexSet, Iter as IndexSetIter};
pub use linear_map::LinearMap;
pub use string::String;

pub use vec::{Vec, VecView};

#[macro_use]
Expand All @@ -107,6 +108,7 @@ mod histbuf;
mod indexmap;
mod indexset;
mod linear_map;
mod slice;
pub mod storage;
pub mod string;
pub mod vec;
Expand Down
38 changes: 38 additions & 0 deletions src/slice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use core::ops;

// FIXME: Remove when `slice_range` feature is stable.
#[track_caller]
#[must_use]
pub fn range<R>(range: R, bounds: ops::RangeTo<usize>) -> ops::Range<usize>
where
R: ops::RangeBounds<usize>,
{
let len = bounds.end;

let start: ops::Bound<&usize> = range.start_bound();
let start = match start {
ops::Bound::Included(&start) => start,
ops::Bound::Excluded(start) => start
.checked_add(1)
.unwrap_or_else(|| panic!("attempted to index slice from after maximum usize")),
ops::Bound::Unbounded => 0,
};

let end: ops::Bound<&usize> = range.end_bound();
let end = match end {
ops::Bound::Included(end) => end
.checked_add(1)
.unwrap_or_else(|| panic!("attempted to index slice up to maximum usize")),
ops::Bound::Excluded(&end) => end,
ops::Bound::Unbounded => len,
};

if start > end {
panic!("slice index starts at {start} but ends at {end}");
}
if end > len {
panic!("range end index {end} out of range for slice of length {len}");
}

ops::Range { start, end }
}
222 changes: 222 additions & 0 deletions src/vec/drain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
use core::{
fmt,
iter::FusedIterator,
mem::{self, size_of},
ptr::{self, NonNull},
slice,
};

use super::VecView;

/// A draining iterator for [`Vec`](super::Vec).
///
/// This `struct` is created by [`Vec::drain`](super::Vec::drain).
/// See its documentation for more.
///
/// # Example
///
/// ```
/// use heapless::{vec, Vec};
///
/// let mut v = Vec::<_, 4>::from_array([0, 1, 2]);
/// let iter: vec::Drain<'_, _> = v.drain(..);
/// ```
pub struct Drain<'a, T: 'a> {
/// Index of tail to preserve
pub(super) tail_start: usize,
/// Length of tail
pub(super) tail_len: usize,
/// Current remaining range to remove
pub(super) iter: slice::Iter<'a, T>,
pub(super) vec: NonNull<VecView<T>>,
}

impl<T: fmt::Debug> fmt::Debug for Drain<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Drain").field(&self.iter.as_slice()).finish()
}
}

impl<'a, T> Drain<'a, T> {
/// Returns the remaining items of this iterator as a slice.
///
/// # Examples
///
/// ```
/// use heapless::{vec, Vec};
///
/// let mut vec = Vec::<_, 3>::from_array(['a', 'b', 'c']);
/// let mut drain = vec.drain(..);
/// assert_eq!(drain.as_slice(), &['a', 'b', 'c']);
/// let _ = drain.next().unwrap();
/// assert_eq!(drain.as_slice(), &['b', 'c']);
/// ```
#[must_use]
pub fn as_slice(&self) -> &[T] {
self.iter.as_slice()
}
}

impl<'a, T> AsRef<[T]> for Drain<'a, T> {
fn as_ref(&self) -> &[T] {
self.as_slice()
}
}

unsafe impl<T: Sync> Sync for Drain<'_, T> {}
unsafe impl<T: Send> Send for Drain<'_, T> {}

impl<T> Iterator for Drain<'_, T> {
type Item = T;

#[inline]
fn next(&mut self) -> Option<T> {
self.iter
.next()
.map(|elt| unsafe { ptr::read(elt as *const _) })
}

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

impl<T> DoubleEndedIterator for Drain<'_, T> {
#[inline]
fn next_back(&mut self) -> Option<T> {
self.iter
.next_back()
.map(|elt| unsafe { ptr::read(elt as *const _) })
}
}

impl<T> Drop for Drain<'_, T> {
fn drop(&mut self) {
/// Moves back the un-`Drain`ed elements to restore the original `Vec`.
struct DropGuard<'r, 'a, T>(&'r mut Drain<'a, T>);

impl<'r, 'a, T> Drop for DropGuard<'r, 'a, T> {
fn drop(&mut self) {
if self.0.tail_len > 0 {
unsafe {
let source_vec = self.0.vec.as_mut();
// memmove back untouched tail, update to new length
let start = source_vec.len();
let tail = self.0.tail_start;
if tail != start {
let dst = source_vec.as_mut_ptr().add(start);
let src = source_vec.as_ptr().add(tail);
ptr::copy(src, dst, self.0.tail_len);
}
source_vec.set_len(start + self.0.tail_len);
}
}
}
}

let iter = mem::take(&mut self.iter);
let drop_len = iter.len();

let mut vec = self.vec;

if size_of::<T>() == 0 {
// ZSTs have no identity, so we don't need to move them around, we only need to drop the correct amount.
// this can be achieved by manipulating the `Vec` length instead of moving values out from `iter`.
unsafe {
let vec = vec.as_mut();
let old_len = vec.len();
vec.set_len(old_len + drop_len + self.tail_len);
vec.truncate(old_len + self.tail_len);
}

return;
}

// ensure elements are moved back into their appropriate places, even when drop_in_place panics
let _guard = DropGuard(self);

if drop_len == 0 {
return;
}

// as_slice() must only be called when iter.len() is > 0 because
// it also gets touched by vec::Splice which may turn it into a dangling pointer
// which would make it and the vec pointer point to different allocations which would
// lead to invalid pointer arithmetic below.
let drop_ptr = iter.as_slice().as_ptr();

unsafe {
// drop_ptr comes from a slice::Iter which only gives us a &[T] but for drop_in_place
// a pointer with mutable provenance is necessary. Therefore we must reconstruct
// it from the original vec but also avoid creating a &mut to the front since that could
// invalidate raw pointers to it which some unsafe code might rely on.
let vec_ptr = vec.as_mut().as_mut_ptr();
// FIXME: Replace with `sub_ptr` once stable.
let drop_offset = (drop_ptr as usize - vec_ptr as usize) / size_of::<T>();
let to_drop = ptr::slice_from_raw_parts_mut(vec_ptr.add(drop_offset), drop_len);
ptr::drop_in_place(to_drop);
}
}
}

impl<T> ExactSizeIterator for Drain<'_, T> {}

impl<T> FusedIterator for Drain<'_, T> {}

#[cfg(test)]
mod tests {
use super::super::Vec;

#[test]
fn drain_front() {
let mut vec = Vec::<_, 8>::from_array([1, 2, 3, 4]);
let mut it = vec.drain(..1);
assert_eq!(it.next(), Some(1));
drop(it);
assert_eq!(vec, &[2, 3, 4]);
}

#[test]
fn drain_middle() {
let mut vec = Vec::<_, 8>::from_array([1, 2, 3, 4]);
let mut it = vec.drain(1..3);
assert_eq!(it.next(), Some(2));
assert_eq!(it.next(), Some(3));
drop(it);
assert_eq!(vec, &[1, 4]);
}

#[test]
fn drain_end() {
let mut vec = Vec::<_, 8>::from_array([1, 2, 3, 4]);
let mut it = vec.drain(3..);
assert_eq!(it.next(), Some(4));
drop(it);
assert_eq!(vec, &[1, 2, 3]);
}

#[test]
fn drain_drop_rest() {
droppable!();

let mut vec = Vec::<_, 8>::from_array([
Droppable::new(),
Droppable::new(),
Droppable::new(),
Droppable::new(),
]);
assert_eq!(Droppable::count(), 4);

let mut iter = vec.drain(2..);
assert_eq!(iter.next().unwrap().0, 3);
drop(iter);
assert_eq!(Droppable::count(), 2);

assert_eq!(vec.len(), 2);
assert_eq!(vec.remove(0).0, 1);
assert_eq!(Droppable::count(), 1);

drop(vec);
assert_eq!(Droppable::count(), 0);
}
}
Loading

0 comments on commit 321c4c7

Please sign in to comment.