Skip to content

Commit

Permalink
ADD: drain() method and Drain iterator
Browse files Browse the repository at this point in the history
This add the drain functionality similar to std Vec's drain to HeaderVec.
The `with_weakfix()` things are not needed for Drain (I was wrong in a earlier commit message)
but they will be required for upcoming Splice functionality.

Since vec::Drain depends on a few nightly features internally but we want to stay compatible
with stable a few things are backported from nightly in `future_slice`. OTOH we can already
stabilize Drain::keep_rest().

Most code was taken from std::vec and minimally adapted to work for HeaderVec.
  • Loading branch information
cehteh committed Jan 30, 2025
1 parent efc96c0 commit 89d6c86
Show file tree
Hide file tree
Showing 4 changed files with 401 additions and 1 deletion.
243 changes: 243 additions & 0 deletions src/drain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
#![cfg(any(feature = "std"))]

Check failure on line 1 in src/drain.rs

View workflow job for this annotation

GitHub Actions / clippy

unneeded sub `cfg` when there is only one condition

use core::{
any::type_name,

Check warning on line 4 in src/drain.rs

View workflow job for this annotation

GitHub Actions / no-std

unused import: `any::type_name`
mem::{self},
ptr::{self, NonNull},
};

use std::{fmt, iter::FusedIterator, mem::ManuallyDrop, slice};

Check failure on line 9 in src/drain.rs

View workflow job for this annotation

GitHub Actions / no-std

can't find crate for `std`

use crate::HeaderVec;

/// A draining iterator for `HeaderVec<H, T>`.
///
/// This `struct` is created by [`HeaderVec::drain`].
/// See its documentation for more.
///
/// # Feature compatibility
///
/// The `drain()` API and [`Drain`] iterator are only available when the `std` feature is
/// enabled.
///
/// # Example
///
/// ```
/// # #[cfg(any(doc, feature = "std"))] {
/// # use header_vec::HeaderVec;
/// let mut hv: HeaderVec<(), _> = HeaderVec::from([0, 1, 2]);
/// let iter: header_vec::Drain<'_, _, _> = hv.drain(..);
/// # }
/// ```
pub struct Drain<'a, H, T> {
/// 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<HeaderVec<H, T>>,
}

impl<H: fmt::Debug, T: fmt::Debug> fmt::Debug for Drain<'_, H, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(&format!(

Check failure on line 44 in src/drain.rs

View workflow job for this annotation

GitHub Actions / no-std

cannot find macro `format` in this scope
"Drain<{}, {}>",
type_name::<H>(),
type_name::<T>()
))
.field("header", unsafe { self.vec.as_ref() })
.field("iter", &self.iter.as_slice())
.finish()
}
}

impl<'a, H, T> Drain<'a, H, T> {

Check failure on line 55 in src/drain.rs

View workflow job for this annotation

GitHub Actions / clippy

the following explicit lifetimes could be elided: 'a
/// Returns the remaining items of this iterator as a slice.
///
/// # Examples
///
/// ```
/// # use header_vec::HeaderVec;
/// let mut hv: HeaderVec<(), _> = HeaderVec::from(['a', 'b', 'c']);
/// let mut drain = hv.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()
}

/// Keep unyielded elements in the source `HeaderVec`.
///
/// # Examples
///
/// ```
/// # use header_vec::HeaderVec;
/// let mut hv: HeaderVec<(), _> = HeaderVec::from(['a', 'b', 'c']);
/// let mut drain = hv.drain(..);
///
/// assert_eq!(drain.next().unwrap(), 'a');
///
/// // This call keeps 'b' and 'c' in the vec.
/// drain.keep_rest();
///
/// // If we wouldn't call `keep_rest()`,
/// // `hv` would be empty.
/// assert_eq!(hv.as_slice(), ['b', 'c']);
/// ```
pub fn keep_rest(self) {
let mut this = ManuallyDrop::new(self);

unsafe {
let source_vec = this.vec.as_mut();

let start = source_vec.len();
let tail = this.tail_start;

let unyielded_len = this.iter.len();
let unyielded_ptr = this.iter.as_slice().as_ptr();

// ZSTs have no identity, so we don't need to move them around.
if std::mem::size_of::<T>() != 0 {
let start_ptr = source_vec.as_mut_ptr().add(start);

// memmove back unyielded elements
if unyielded_ptr != start_ptr {
let src = unyielded_ptr;
let dst = start_ptr;

ptr::copy(src, dst, unyielded_len);
}

// memmove back untouched tail
if tail != (start + unyielded_len) {
let src = source_vec.as_ptr().add(tail);
let dst = start_ptr.add(unyielded_len);
ptr::copy(src, dst, this.tail_len);
}
}

source_vec.set_len(start + unyielded_len + this.tail_len);
}
}
}

impl<'a, H, T> AsRef<[T]> for Drain<'a, H, T> {

Check failure on line 128 in src/drain.rs

View workflow job for this annotation

GitHub Actions / clippy

the following explicit lifetimes could be elided: 'a
fn as_ref(&self) -> &[T] {
self.as_slice()
}
}

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

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

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

impl<'r, 'a, H, T> Drop for DropGuard<'r, 'a, H, T> {

Check failure on line 166 in src/drain.rs

View workflow job for this annotation

GitHub Actions / clippy

the following explicit lifetimes could be elided: 'r, 'a
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 src = source_vec.as_ptr().add(tail);
let dst = source_vec.as_mut_ptr().add(start);
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;

// unstable: if T::IS_ZST { instead we use size_of
if mem::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();

// PLANNED: let drop_offset = drop_ptr.sub_ptr(vec_ptr); is in nightly
let drop_offset = usize::try_from(drop_ptr.offset_from(vec_ptr)).unwrap_unchecked();
let to_drop = ptr::slice_from_raw_parts_mut(vec_ptr.add(drop_offset), drop_len);
ptr::drop_in_place(to_drop);
}
}
}

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

// PLANNED: unstable features
// impl<H, T> ExactSizeIterator for Drain<'_, H, T> {
// fn is_empty(&self) -> bool {
// self.iter.is_empty()
// }
// }
//
// #[unstable(feature = "trusted_len", issue = "37572")]
// unsafe impl<H, T> TrustedLen for Drain<'_, H, T> {}
//
61 changes: 61 additions & 0 deletions src/future_slice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! This module re-implements a unstable slice functions, these should be removed once they
//! are stabilized. These is copy-pasted with slight modifications from std::slice for
//! functions that do not need language magic.
use std::ops;

Check failure on line 5 in src/future_slice.rs

View workflow job for this annotation

GitHub Actions / no-std

can't find crate for `std`

#[track_caller]
#[must_use]
pub(crate) fn range<R>(range: R, bounds: ops::RangeTo<usize>) -> ops::Range<usize>
where
R: ops::RangeBounds<usize>,
{
let len = bounds.end;

let start = match range.start_bound() {
ops::Bound::Included(&start) => start,
ops::Bound::Excluded(start) => start
.checked_add(1)
.unwrap_or_else(|| slice_start_index_overflow_fail()),
ops::Bound::Unbounded => 0,
};

let end = match range.end_bound() {
ops::Bound::Included(end) => end
.checked_add(1)
.unwrap_or_else(|| slice_end_index_overflow_fail()),
ops::Bound::Excluded(&end) => end,
ops::Bound::Unbounded => len,
};

if start > end {
slice_index_order_fail(start, end);
}
if end > len {
slice_end_index_len_fail(end, len);
}

ops::Range { start, end }
}

#[track_caller]
const fn slice_start_index_overflow_fail() -> ! {
panic!("attempted to index slice from after maximum usize");

Check failure on line 43 in src/future_slice.rs

View workflow job for this annotation

GitHub Actions / no-std

cannot find macro `panic` in this scope
}

#[track_caller]
const fn slice_end_index_overflow_fail() -> ! {
panic!("attempted to index slice up to maximum usize");

Check failure on line 48 in src/future_slice.rs

View workflow job for this annotation

GitHub Actions / no-std

cannot find macro `panic` in this scope
}

#[track_caller]
fn slice_index_order_fail(index: usize, end: usize) -> ! {
panic!("slice index start is larger than end, slice index starts at {index} but ends at {end}")

Check failure on line 53 in src/future_slice.rs

View workflow job for this annotation

GitHub Actions / no-std

cannot find macro `panic` in this scope
}

#[track_caller]
fn slice_end_index_len_fail(index: usize, len: usize) -> ! {
panic!(

Check failure on line 58 in src/future_slice.rs

View workflow job for this annotation

GitHub Actions / no-std

cannot find macro `panic` in this scope
"slice end index is out of range for slice, range end index {index} out of range for slice of length {len}"
)
}
Loading

0 comments on commit 89d6c86

Please sign in to comment.