diff --git a/CHANGELOG.md b/CHANGELOG.md index caa938303f..0742f0ff31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/deque.rs b/src/deque.rs index 874fbb7907..3e90fb0216 100644 --- a/src/deque.rs +++ b/src/deque.rs @@ -963,6 +963,94 @@ impl + ?Sized> DequeInner { } } + /// 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. @@ -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(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 = 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" + ); + } 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!();