From fbe5f3ff3cc1037eb91aca76603ced66381c2bab Mon Sep 17 00:00:00 2001 From: nullstalgia Date: Wed, 1 Oct 2025 00:37:30 -0700 Subject: [PATCH] Add truncate and retain(_mut) to Deque --- CHANGELOG.md | 3 + src/deque.rs | 432 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 435 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c06dcfffe2..26930e577c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Implement `TryFrom` for `Deque` from array. - Switch from `serde` to `serde_core` for enabling faster compilations. - Implement `Zeroize` trait for all data structures with the `zeroize` feature to securely clear sensitive data from memory. +- Added `truncate` to `Deque` +- Added `retain` to `Deque` +- Added `retain_mut` to `Deque` ## [v0.9.1] - 2025-08-19 diff --git a/src/deque.rs b/src/deque.rs index ec12bbf8d6..fe16725386 100644 --- a/src/deque.rs +++ b/src/deque.rs @@ -811,6 +811,184 @@ impl + ?Sized> DequeInner { inner: start.iter_mut().chain(end), } } + + /// Shortens the deque, keeping the first `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.truncate(1); + /// assert_eq!(buf.make_contiguous(), [5]); + /// ``` + pub fn truncate(&mut self, len: usize) { + /// Runs the destructor for all items in the slice when it gets dropped (gracefully or + /// during unwinding). + struct Dropper<'a, T>(&'a mut [T]); + + impl<'a, T> Drop for Dropper<'a, T> { + fn drop(&mut self) { + unsafe { + ptr::drop_in_place(self.0); + } + } + } + + // Safety: + // * Any slice passed to `drop_in_place` is valid; the second case has + // `len <= front.len()` and returning on `len > self.storage_len()` ensures + // `begin <= back.len()` in the first case + // * Deque front/back cursors are moved before calling `drop_in_place`, + // so no value is dropped twice if `drop_in_place` panics + unsafe { + // 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` desires to keep elements past front's entire length, + // then only back's contents will need to be dropped + // as the two slices combined should be more than `len`. + if len > front.len() { + let begin = len - front.len(); + let drop_back = back.get_unchecked_mut(begin..) as *mut _; + + // Self::to_physical_index returns the index `len` units _after_ the front cursor, + // meaning we can use it to find the decremented index for `back` for non-contiguous deques, + // as well as determine where the new "cap" for front needs to be placed for contiguous deques. + self.back = self.to_physical_index(len); + self.full = false; + + ptr::drop_in_place(drop_back); + } else { + // Otherwise, we know back's entire contents need to be dropped, + // since the desired length never reaches into it. + let drop_back = back as *mut _; + let drop_front = front.get_unchecked_mut(len..) as *mut _; + + self.back = self.to_physical_index(len); + self.full = false; + + // If `drop_front` causes a panic, the Dropper will still be called to drop it's slice during unwinding. + // In either case, front will always be dropped before back. + 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. + /// This method operates in place, visiting each element exactly once in the + /// original order, and preserves the order of the retained elements. + /// + /// # Examples + /// + /// ``` + /// use heapless::Deque; + /// + /// let mut buf: Deque<_, 10> = Deque::new(); + /// buf.extend(1..5); + /// buf.retain(|&x| x % 2 == 0); + /// assert_eq!(buf.make_contiguous(), [2, 4]); + /// ``` + /// + /// Because the elements are visited exactly once in the original order, + /// external state may be used to decide which elements to keep. + /// + /// ``` + /// use heapless::Deque; + /// + /// let mut buf: Deque<_, 10> = Deque::new(); + /// buf.extend(1..6); + /// + /// let keep = [false, true, true, false, true]; + /// let mut iter = keep.iter(); + /// buf.retain(|_| *iter.next().unwrap()); + /// assert_eq!(buf.make_contiguous(), [2, 3, 5]); + /// ``` + pub fn retain(&mut self, mut f: F) + where + F: FnMut(&T) -> bool, + { + self.retain_mut(|elem| f(elem)); + } + + /// Retains only the elements specified by the predicate. + /// + /// In other words, remove all elements `e` for which `f(&mut e)` returns false. + /// This method operates in place, visiting each element exactly once in the + /// original order, and preserves the order of the retained elements. + /// + /// # Examples + /// + /// ``` + /// use heapless::Deque; + /// + /// let mut buf: Deque<_, 10> = Deque::new(); + /// buf.extend(1..5); + /// buf.retain_mut(|x| { + /// if *x % 2 == 0 { + /// *x += 1; + /// true + /// } else { + /// false + /// } + /// }); + /// assert_eq!(buf.make_contiguous(), [3, 5]); + /// ``` + pub fn retain_mut(&mut self, mut f: F) + where + F: FnMut(&mut T) -> bool, + { + let len = self.storage_len(); + let mut idx = 0; + let mut cur = 0; + + // Stage 1: Check if all values can be retained. + while cur < len { + let val = self + .get_mut(cur) + .expect("cur was checked to be less than deque's length"); + if !f(val) { + cur += 1; + break; + } + + cur += 1; + idx += 1; + } + // Stage 2: Swap retained values into current idx, building a contiguous chunk from 0 to idx. + while cur < len { + let val = self + .get_mut(cur) + .expect("cur was checked to be less than deque's length"); + if !f(val) { + cur += 1; + continue; + } + + self.swap(idx, cur); + cur += 1; + idx += 1; + } + // Stage 3: Truncate any moved values after idx. + if cur != idx { + self.truncate(idx); + } + } } /// Iterator over the contents of a [`Deque`] @@ -1703,4 +1881,258 @@ mod tests { assert_eq!(deque.len(), 0); assert!(deque.is_empty()); } + + // Checking that no invalid destructors are called with empty Deques + #[test] + fn truncate_empty() { + droppable!(); + + const LEN: usize = 1; + let mut tester: Deque<_, LEN> = Deque::new(); + + // Truncate to 0 from 0 + tester.truncate(0); + assert_eq!(tester.len(), 0); + assert_eq!(Droppable::count(), 0); + + // Truncate to 123 from 0 (thus clamping back down to 0) + tester.truncate(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); + + // Truncate to 0 from 1 + tester.truncate(0); + assert_eq!(tester.len(), 0); + assert_eq!(Droppable::count(), 0); + } + + // Testing truncation with contiguous Deques + #[test] + fn truncate_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()); + } + + // Truncating past the elements present, no change. + tester.truncate(10); + let lens = slice_lengths(tester.as_slices()); + assert_eq!(lens, (5, 0)); + assert_eq!(Droppable::count(), 5); + + // Truncating equal to elements present, no change. + tester.truncate(5); + let lens = slice_lengths(tester.as_slices()); + assert_eq!(lens, (5, 0)); + assert_eq!(Droppable::count(), 5); + + // Truncating to empty. + tester.truncate(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); + + // Truncate into the middle of elements. + tester.truncate(3); + let lens = slice_lengths(tester.as_slices()); + assert_eq!(lens, (3, 0)); + assert_eq!(Droppable::count(), 3); + + // Truncating to empty. + tester.truncate(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.truncate(10); + let lens = slice_lengths(tester.as_slices()); + assert_eq!(lens, (5, 0)); + assert_eq!(Droppable::count(), 5); + + tester.truncate(5); + let lens = slice_lengths(tester.as_slices()); + assert_eq!(lens, (5, 0)); + assert_eq!(Droppable::count(), 5); + + tester.truncate(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.truncate(3); + let lens = slice_lengths(tester.as_slices()); + assert_eq!(lens, (3, 0)); + assert_eq!(Droppable::count(), 3); + + tester.truncate(0); + assert_eq!(tester.len(), 0); + assert_eq!(Droppable::count(), 0); + } + + // Testing truncation with non-contiguous Deques + #[test] + fn truncate_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()); + } + + // Truncating past the elements present, no change. + tester.truncate(10); + assert_eq!(tester.as_slices(), (&[3, 2, 1][..], &[1, 2, 3][..])); + println!("{} {}", tester.front, tester.back); + // Truncating equal to elements present, no change. + tester.truncate(6); + assert_eq!(tester.as_slices(), (&[3, 2, 1][..], &[1, 2, 3][..])); + + // Truncating to empty. + tester.truncate(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][..])); + + // Truncating only part of back, retaining front and part of back. + tester.truncate(5); + assert_eq!(tester.as_slices(), (&[3, 2, 1][..], &[1, 2][..])); + + // Replacing the truncated element. + assert!(tester.push_back(3).is_ok()); + assert_eq!(tester.as_slices(), (&[3, 2, 1][..], &[1, 2, 3][..])); + + // Truncating away all of back's contents, but retaining all of front's contents. + tester.truncate(3); + assert_eq!(tester.as_slices(), (&[3, 2, 1][..], &[][..])); + + // Replacing the truncated elements. + for y in 1..=3 { + assert!(tester.push_back(y).is_ok()); + } + assert_eq!(tester.as_slices(), (&[3, 2, 1][..], &[1, 2, 3][..])); + + // Truncating into front, thus also dropping all of back. + tester.truncate(2); + assert_eq!(tester.as_slices(), (&[3, 2][..], &[][..])); + + // Truncating to empty. + tester.truncate(0); + assert_eq!(tester.as_slices(), (&[][..], &[][..])); + + // Should remain empty. + tester.truncate(123); + assert_eq!(tester.as_slices(), (&[][..], &[][..])); + } + + // Tests that each element's destructor is called when being truncated. + #[test] + fn truncate_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.truncate(TRUNC); + assert_eq!(tester.len(), TRUNC); + assert_eq!(Droppable::count(), TRUNC as i32); + + tester.truncate(0); + assert_eq!(tester.len(), 0); + assert_eq!(Droppable::count(), 0); + } + } + + #[test] + fn retain() { + droppable!(); + + const LEN: usize = 20; + 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((index, Droppable::new())).is_ok()); + } else { + assert!(tester.push_back((index, Droppable::new())).is_ok()); + } + } + assert_eq!(Droppable::count(), LEN as i32); + + tester.retain(|(x, _)| *x >= 10); + + assert_eq!(tester.len(), 10); + assert_eq!(Droppable::count(), 10); + } + } }