Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]

- Added `resize_with` to `Vec`
- Added `retain_back` (aka `truncate_front`) to `Deque`

## [v0.9.3] 2025-04-15

Expand Down
321 changes: 321 additions & 0 deletions src/deque.rs
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,94 @@ impl<T, S: VecStorage<T> + ?Sized> DequeInner<T, S> {
}
}

/// Shortens the deque, keeping the last `len` elements and dropping
/// the rest.
///
/// If `len` is greater or equal to the deque's current length, this has
/// no effect.
///
/// # Examples
///
/// ```
/// use heapless::Deque;
///
/// let mut buf: Deque<_, 5> = Deque::new();
/// buf.push_back(5);
/// buf.push_back(10);
/// buf.push_back(15);
/// buf.retain_back(1);
/// assert_eq!(buf.make_contiguous(), [15]);
/// ```
#[doc(alias = "truncate_front")]
pub fn retain_back(&mut self, len: usize) {
// If new desired length is greater or equal, we don't need to act.
if len >= self.storage_len() {
return;
}

let (front, back) = self.as_mut_slices();

if len > back.len() {
// The `back` slice remains unchanged (`len` intends to retain more elements than `back`
// contains), front.len() + back.len() == self.len, so `end` is non-negative
// and end <= front.len().
let end = front.len() - (len - back.len());

// Safety: `end` is always less than or equal to `front.len()`
let drop_front = core::ptr::from_mut(unsafe { front.get_unchecked_mut(..end) });

// Turn the back slice into the front slice by
// placing the front cursor under `self.back`, spaced by `end` elements.
self.front = self.to_physical_index(end);
self.full = false;

// Safety: Any slice passed to `drop_in_place` is valid:
// * Only valid elements previously contained within `front` are present.
// * The elements are valid for reading/writing, originating from a &mut [T].
// * Deque front cursor is moved before calling `drop_in_place`, so no value is dropped
// twice if `drop_in_place` panics.
unsafe { ptr::drop_in_place(drop_front) }
} else {
/// Runs the destructor for all items in the slice when it gets dropped
/// (gracefully at scope's end or during panic unwinding).
struct Dropper<'a, T>(&'a mut [T]);
impl<'a, T> Drop for Dropper<'a, T> {
fn drop(&mut self) {
// Safety:
// The slice given to the Dropper should only contain elements
// that have been truncated by the movement of the front cursor.
unsafe { ptr::drop_in_place(self.0) }
}
}

// We intend to only keep fewer elements than `back` contains,
// so all elements within `front` will be dropped,
// before proceeding to truncate values within `back`.
let drop_front = core::ptr::from_mut(front);

// 'end' is non-negative by the conditional above
let end = back.len() - len;

// Safety: `end` is always less than or equal to `back.len()`
let drop_back = core::ptr::from_mut(unsafe { back.get_unchecked_mut(..end) });

// Turn the back slice into the front slice,
// additionally shortening its length to `len`.
self.front = self.to_physical_index(self.storage_len() - len);
self.full = false;

// Safety:
// * If `drop_front` causes a panic, the Dropper will still be called to drop its slice
// during unwinding. In either case, front will always be dropped before back.
// * Deque front cursor is moved before calling `drop_in_place`, so no value is dropped
// twice if either `drop_in_place` panics.
unsafe {
let _back_dropper = Dropper(&mut *drop_back);
ptr::drop_in_place(drop_front);
}
}
}

/// Retains only the elements specified by the predicate.
///
/// In other words, remove all elements `e` for which `f(&e)` returns false.
Expand Down Expand Up @@ -2252,6 +2340,239 @@ mod tests {
}
}

// Checking that no invalid destructors are called with empty Deques
#[test]
fn retain_back_empty() {
droppable!();

const LEN: usize = 1;
let mut tester: Deque<_, LEN> = Deque::new();

// Retaining 0 from 0
tester.retain_back(0);
assert_eq!(tester.len(), 0);
assert_eq!(Droppable::count(), 0);

// Retaining 123 from 0 (thus clamping back down to 0)
tester.retain_back(123);
assert_eq!(tester.len(), 0);
assert_eq!(Droppable::count(), 0);

// Ensure state is still valid by pushing one element in and then truncating again
assert!(tester.push_front(Droppable::new()).is_ok());
assert_eq!(tester.len(), 1);
assert_eq!(Droppable::count(), 1);

// Retaining 0 from 1
tester.retain_back(0);
assert_eq!(tester.len(), 0);
assert_eq!(Droppable::count(), 0);
}

// Testing retaining the back elements of contiguous Deques
#[test]
fn retain_back_contiguous() {
droppable!();

fn slice_lengths<T>(slices: (&[T], &[T])) -> (usize, usize) {
let (a, b) = slices;
(a.len(), b.len())
}

const LEN: usize = 20;
let mut tester: Deque<_, LEN> = Deque::new();

// Filling from front.
for _ in 0..5 {
assert!(tester.push_front(Droppable::new()).is_ok());
}

// Retaining more than the elements present, no change.
tester.retain_back(10);
let lens = slice_lengths(tester.as_slices());
assert_eq!(lens, (5, 0));
assert_eq!(Droppable::count(), 5);

// Retaining equal to elements present, no change.
tester.retain_back(5);
let lens = slice_lengths(tester.as_slices());
assert_eq!(lens, (5, 0));
assert_eq!(Droppable::count(), 5);

// Retaining none.
tester.retain_back(0);
assert_eq!(tester.len(), 0);
assert_eq!(Droppable::count(), 0);

// Refill from front.
for _ in 0..5 {
assert!(tester.push_front(Droppable::new()).is_ok());
}

let lens = slice_lengths(tester.as_slices());
assert_eq!(lens, (5, 0));
assert_eq!(Droppable::count(), 5);

// Retaining up to the middle of elements.
tester.retain_back(3);
let lens = slice_lengths(tester.as_slices());
assert_eq!(lens, (3, 0));
assert_eq!(Droppable::count(), 3);

// Retaining none.
tester.retain_back(0);
assert_eq!(tester.len(), 0);
assert_eq!(Droppable::count(), 0);

// Resetting cursors.
tester.clear();

// Filling from back...
for _ in 0..5 {
assert!(tester.push_back(Droppable::new()).is_ok());
}

tester.retain_back(10);
let lens = slice_lengths(tester.as_slices());
assert_eq!(lens, (5, 0));
assert_eq!(Droppable::count(), 5);

tester.retain_back(5);
let lens = slice_lengths(tester.as_slices());
assert_eq!(lens, (5, 0));
assert_eq!(Droppable::count(), 5);

tester.retain_back(0);
assert_eq!(tester.len(), 0);
assert_eq!(Droppable::count(), 0);

for _ in 0..5 {
assert!(tester.push_back(Droppable::new()).is_ok());
}

let lens = slice_lengths(tester.as_slices());
assert_eq!(lens, (5, 0));
assert_eq!(Droppable::count(), 5);

tester.retain_back(3);
let lens = slice_lengths(tester.as_slices());
assert_eq!(lens, (3, 0));
assert_eq!(Droppable::count(), 3);

tester.retain_back(0);
assert_eq!(tester.len(), 0);
assert_eq!(Droppable::count(), 0);
}

// Testing retention with non-contiguous Deques
#[test]
fn retain_back_non_contiguous() {
const LEN: usize = 20;
let mut tester: Deque<u8, LEN> = Deque::new();

// Filling non-contiguously.
//
// Expecting [3, 2, 1, 1, 2, 3]
for x in 1..=3 {
assert!(tester.push_front(x).is_ok());
}
for y in 1..=3 {
assert!(tester.push_back(y).is_ok());
}

// Retaining more than the elements present, no change.
tester.retain_back(10);
assert_eq!(tester.as_slices(), (&[3, 2, 1][..], &[1, 2, 3][..]));

// Retaining equal to elements present, no change.
tester.retain_back(6);
assert_eq!(tester.as_slices(), (&[3, 2, 1][..], &[1, 2, 3][..]));

// Retaining none.
tester.retain_back(0);
assert_eq!(tester.as_slices(), (&[][..], &[][..]));

// Resetting cursors.
tester.clear();

// Refilling.
for x in 1..=3 {
assert!(tester.push_front(x).is_ok());
}
for y in 1..=3 {
assert!(tester.push_back(y).is_ok());
}

assert_eq!(tester.as_slices(), (&[3, 2, 1][..], &[1, 2, 3][..]));

// Retaining all of back and part of front.
tester.retain_back(5);
assert_eq!(tester.as_slices(), (&[2, 1][..], &[1, 2, 3][..]));

// Replacing the truncated element.
assert!(tester.push_front(3).is_ok());
assert_eq!(tester.as_slices(), (&[3, 2, 1][..], &[1, 2, 3][..]));

// Retaining only back contents, thus making it the front slice.
tester.retain_back(3);
assert_eq!(tester.as_slices(), (&[1, 2, 3][..], &[][..]));

// Replacing the truncated elements.
for y in 1..=3 {
assert!(tester.push_front(y).is_ok());
}
assert_eq!(tester.as_slices(), (&[3, 2, 1][..], &[1, 2, 3][..]));

// Retaining only some of back, thus also dropping all of front,
// again making it the front slice as a result.
tester.retain_back(2);
assert_eq!(tester.as_slices(), (&[2, 3][..], &[][..]));

// Retaining none.
tester.retain_back(0);
assert_eq!(tester.as_slices(), (&[][..], &[][..]));

// Should remain empty.
tester.retain_back(123);
assert_eq!(tester.as_slices(), (&[][..], &[][..]));
}

// Tests that each element's destructor is called when not being retained.
#[test]
fn retain_back_drop_count() {
droppable!();

const LEN: usize = 20;
const TRUNC: usize = 3;
for push_front_amt in 0..=LEN {
let mut tester: Deque<_, LEN> = Deque::new();
for index in 0..LEN {
if index < push_front_amt {
assert!(
tester.push_front(Droppable::new()).is_ok(),
"deque must have room for all {LEN} entries"
);
Comment thread
zeenix marked this conversation as resolved.
} else {
assert!(
tester.push_back(Droppable::new()).is_ok(),
"deque must have room for all {LEN} entries"
);
}
}

assert_eq!(Droppable::count(), LEN as i32);

tester.retain_back(TRUNC);
assert_eq!(tester.len(), TRUNC);
assert_eq!(Droppable::count(), TRUNC as i32);

tester.retain_back(0);

assert_eq!(tester.len(), 0);
assert_eq!(Droppable::count(), 0);
}
}

#[test]
fn retain() {
droppable!();
Expand Down
Loading