Skip to main content

turbo_tasks/
tiny_vec.rs

1//! A `Vec`-shaped container with `u8` length and capacity, sized 16 B on 64-bit instead of 24 B.
2//!
3//! Used by `#[task_storage]` for `TaskStorage`'s lazy-fields field, which holds at most ~25
4//! elements (one per declared lazy field in the schema). With several million task storages
5//! live during a typical Next.js build, the 8 B saved per task adds up to dozens of MB of
6//! resident memory.
7//!
8//! The API is intentionally a strict subset of `Vec` covering only what the task-storage
9//! callers and the `#[task_storage]` macro emit need: `len`, `iter`, `iter_mut`, `push`,
10//! `swap_remove`, `last_mut`, `index`, `index_mut`, `extend`, `reserve`, `retain_mut`,
11//! `Default`, `Debug`, `ShrinkToFit`. No `Clone` or `PartialEq` — `TaskStorage` doesn't
12//! derive them.
13//!
14//! ## Capacity
15//!
16//! `TinyVec<T, MAX>` is statically capped at `MAX <= 255` elements. Pushing past `MAX`
17//! panics. Growth doubles until it would exceed `MAX`, then caps at exactly `MAX`. The
18//! default `MAX = 255` covers any container that fits the type's `u8` cap.
19//!
20//! For `TaskStorage::lazy` the schema emits `TinyVec<LazyField, 25>`, which tightens the
21//! steady-state allocation: a fully-populated lazy vec ends at cap=25 instead of cap=32
22//! (the next power of two), saving 7 slots × `size_of::<LazyField>()` ≈ 336 B per such
23//! task.
24
25use std::{
26    alloc::{Layout, alloc, dealloc, handle_alloc_error},
27    fmt,
28    marker::PhantomData,
29    mem::ManuallyDrop,
30    ptr::{self, NonNull},
31};
32
33/// Compact `Vec`-shaped container with a statically-bounded capacity; see module docs for
34/// rationale. `MAX` defaults to `u8::MAX = 255` (the largest value the `u8` cap field can hold).
35pub struct TinyVec<T, const MAX: u8 = { u8::MAX }> {
36    /// Heap pointer. Dangling (uninitialized) when `cap == 0`.
37    ptr: NonNull<T>,
38    len: u8,
39    cap: u8,
40    /// Marker so we own `T` for drop-check purposes (matches `Vec<T>`'s variance/dropck).
41    _marker: PhantomData<T>,
42}
43
44// SAFETY: same as `Vec<T>` — we own a heap allocation of `T`s, and the only shared state is via
45// the `ptr` which is unique to this `TinyVec`.
46unsafe impl<T: Send, const MAX: u8> Send for TinyVec<T, MAX> {}
47unsafe impl<T: Sync, const MAX: u8> Sync for TinyVec<T, MAX> {}
48
49impl<T, const MAX: u8> Default for TinyVec<T, MAX> {
50    fn default() -> Self {
51        Self::new()
52    }
53}
54
55impl<T, const MAX: u8> TinyVec<T, MAX> {
56    // Compile-time assertion that `MAX > 0`. Referenced inside `new()` so it gets evaluated
57    // at monomorphization time; the panic message becomes a compile error for any
58    // `TinyVec<T, 0>` instantiation rather than a runtime panic on the first call.
59    const _ASSERT_MAX_NONZERO: () = assert!(MAX > 0, "TinyVec MAX must be > 0");
60
61    const fn new() -> Self {
62        // Force evaluation of the static assertion at this generic's monomorphization.
63        // The `let` binding to `()` keeps the const visited; clippy's `let_unit_value` lint
64        // is allowed here because that's intentional.
65        #[allow(clippy::let_unit_value)]
66        let _: () = Self::_ASSERT_MAX_NONZERO;
67        Self {
68            ptr: NonNull::dangling(),
69            len: 0,
70            cap: 0,
71            _marker: PhantomData,
72        }
73    }
74
75    /// Retains only the elements for which the predicate returns `true`. See
76    /// [`Vec::retain_mut`] for semantics including panic safety.
77    ///
78    /// Delegates to `Vec::retain_mut`. Implementing retain_mut directly requires a
79    /// panic-safe partial-shift dance that's the trickiest unsafe code in this module; the
80    /// `Vec` version is identical in shape but has been hand-tested in the standard
81    /// library. Round-tripping through `Vec` for this one operation is worth the soundness
82    /// improvement, especially since `retain_mut` is cold relative to `push`.
83    pub fn retain_mut(&mut self, f: impl FnMut(&mut T) -> bool) {
84        if self.len == 0 {
85            return;
86        }
87
88        // Panic safety: transfer buffer ownership to the local `Vec` *before* the closure
89        // can panic. Zeroing `cap` first means our `Drop` becomes a no-op until we restore
90        // it below — if `f` panics, `vec`'s Drop frees the buffer exactly once and our
91        // Drop (which may run during continued unwinding) does nothing.
92        let ptr = self.ptr.as_ptr();
93        let len = self.len as usize;
94        let cap = self.cap as usize;
95        self.cap = 0;
96        self.len = 0;
97
98        // SAFETY: by struct invariant, `(ptr, len, cap)` is a valid `Vec::from_raw_parts`
99        // triple.
100        let mut vec = unsafe { Vec::from_raw_parts(ptr, len, cap) };
101        vec.retain_mut(f);
102
103        // No panic. Take ownership of the (possibly element-dropped) buffer back.
104        // `retain_mut` never grows, so `new_cap == cap`.
105        let (new_ptr, new_len, new_cap) = vec.into_raw_parts();
106        debug_assert_eq!(new_cap, cap);
107        // SAFETY: `Vec::into_raw_parts` returns a non-null pointer; same buffer as on entry.
108        self.ptr = unsafe { NonNull::new_unchecked(new_ptr) };
109        self.len = new_len as u8;
110        self.cap = new_cap as u8;
111    }
112
113    #[inline]
114    pub fn len(&self) -> usize {
115        self.len as usize
116    }
117
118    /// Pair to [`len`] (kept inherent so clippy's `len_without_is_empty` lint is satisfied;
119    /// it's also reachable through `Deref<[T]>::is_empty`).
120    #[inline]
121    pub fn is_empty(&self) -> bool {
122        self.len == 0
123    }
124
125    // `capacity` is exposed only to tests; external callers don't need it.
126    #[cfg(test)]
127    #[inline]
128    fn capacity(&self) -> usize {
129        self.cap as usize
130    }
131
132    // `iter`, `iter_mut`, `last_mut`, indexing, and `.is_empty()` slice-style usage are
133    // reachable through `Deref`/`DerefMut` to `[T]`. No need for inherent methods.
134
135    #[inline]
136    fn as_slice(&self) -> &[T] {
137        // SAFETY: ptr is valid for `len` initialized elements; if len == 0, slicing the
138        // dangling pointer is allowed by `from_raw_parts`.
139        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len()) }
140    }
141
142    #[inline]
143    fn as_mut_slice(&mut self) -> &mut [T] {
144        // SAFETY: same as `as_slice`; we hold `&mut self`.
145        unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len()) }
146    }
147
148    /// Appends `value`. Panics if `len == MAX`.
149    pub fn push(&mut self, value: T) {
150        if self.len == self.cap {
151            // grow_by_one asserts inside realloc_to when new_cap > MAX. The check below
152            // happens before the cold-path call so we panic with a clearer message when the
153            // container is already saturated.
154            assert!(
155                (self.len as usize) < MAX as usize,
156                "TinyVec capacity overflow: already at MAX = {MAX}",
157            );
158            self.grow_by_one();
159        }
160        // SAFETY: `len < cap` after the grow; the slot at index `len` is uninitialized and we
161        // initialize it here.
162        unsafe {
163            ptr::write(self.ptr.as_ptr().add(self.len()), value);
164        }
165        self.len += 1;
166    }
167
168    /// Removes the element at `idx` by swapping it with the last and popping. O(1).
169    /// Panics if `idx` is out of bounds (matching `Vec::swap_remove`).
170    pub fn swap_remove(&mut self, idx: usize) -> T {
171        let len = self.len();
172        assert!(idx < len, "swap_remove index out of bounds: {idx} >= {len}");
173        // SAFETY: `idx < len`; we read out the value and then either swap or shrink.
174        unsafe {
175            let last = self.ptr.as_ptr().add(len - 1);
176            let hole = self.ptr.as_ptr().add(idx);
177            let value = ptr::read(hole);
178            if idx != len - 1 {
179                ptr::copy_nonoverlapping(last, hole, 1);
180            }
181            self.len -= 1;
182            value
183        }
184    }
185
186    /// Reserves capacity for at least `additional` more elements. No-op if already sufficient.
187    /// Panics if the resulting capacity would exceed `MAX`.
188    ///
189    /// Private: used by `extend_exact` internally; no external callers.
190    fn reserve(&mut self, additional: usize) {
191        let needed = self.len() + additional;
192        if needed <= self.cap as usize {
193            return;
194        }
195        // Round up to next power of two (min 4), but never exceed MAX.
196        let target = needed.next_power_of_two().max(4).min(MAX as usize);
197        self.realloc_to(target);
198    }
199
200    /// Grow the buffer by at least one slot. The first allocation jumps to 4 to amortize the
201    /// initial pushes; subsequent growths double, capped at `MAX`.
202    #[cold]
203    #[inline(never)]
204    fn grow_by_one(&mut self) {
205        let doubled = if self.cap == 0 {
206            4
207        } else {
208            (self.cap as usize) * 2
209        };
210        let new_cap = doubled.min(MAX as usize);
211        self.realloc_to(new_cap);
212    }
213
214    fn realloc_to(&mut self, new_cap: usize) {
215        assert!(
216            new_cap <= MAX as usize,
217            "TinyVec capacity overflow: requested {new_cap}, max {MAX}",
218        );
219        if new_cap == self.cap as usize {
220            return;
221        }
222        if size_of::<T>() == 0 {
223            // Zero-sized types: no allocation needed; just bump cap.
224            self.cap = new_cap as u8;
225            return;
226        }
227
228        // Allocate new buffer.
229        let new_layout = Layout::array::<T>(new_cap).expect("TinyVec layout overflow");
230        // SAFETY: Layout has nonzero size because new_cap > 0 (or we'd not be here) and T is
231        // nonzero-sized (handled above).
232        let new_ptr = unsafe { alloc(new_layout) } as *mut T;
233        let new_ptr = match NonNull::new(new_ptr) {
234            Some(p) => p,
235            None => handle_alloc_error(new_layout),
236        };
237
238        // Move elements over.
239        if self.cap > 0 {
240            // SAFETY: old buffer holds `len` initialized Ts; copy them to the new buffer's
241            // prefix (which is uninitialized).
242            unsafe {
243                ptr::copy_nonoverlapping(self.ptr.as_ptr(), new_ptr.as_ptr(), self.len());
244            }
245            self.deallocate_old();
246        }
247
248        self.ptr = new_ptr;
249        self.cap = new_cap as u8;
250    }
251
252    /// Deallocates the current heap buffer without dropping the elements (caller must have
253    /// already moved or dropped them). No-op if `cap == 0`.
254    ///
255    /// `#[inline]` so the `cap == 0` early return collapses at the `Drop` call site for
256    /// empty containers — saves a function call on what is otherwise a one-instruction path.
257    #[inline]
258    fn deallocate_old(&mut self) {
259        if self.cap == 0 || size_of::<T>() == 0 {
260            return;
261        }
262        let old_layout =
263            Layout::array::<T>(self.cap as usize).expect("TinyVec layout was valid when allocated");
264        // SAFETY: ptr came from `alloc` with this layout in `realloc_to`.
265        unsafe {
266            dealloc(self.ptr.as_ptr() as *mut u8, old_layout);
267        }
268    }
269
270    /// Shrinks the heap buffer to fit `len`, freeing it entirely if `len == 0`.
271    pub fn shrink_to_fit(&mut self) {
272        if (self.len as usize) == (self.cap as usize) {
273            return;
274        }
275        if self.len == 0 {
276            // Free the buffer entirely.
277            self.deallocate_old();
278            self.ptr = NonNull::dangling();
279            self.cap = 0;
280            return;
281        }
282        let new_cap = self.len as usize;
283        // Allocate a smaller buffer, copy, free old.
284        let new_layout = Layout::array::<T>(new_cap).expect("TinyVec layout overflow");
285        // SAFETY: layout is nonzero (new_cap > 0, T is nonzero-sized — ZST early-returned via the
286        // len == cap check above since cap = 0 for ZSTs would also trigger the equal branch).
287        let new_ptr = unsafe { alloc(new_layout) } as *mut T;
288        let new_ptr = match NonNull::new(new_ptr) {
289            Some(p) => p,
290            None => handle_alloc_error(new_layout),
291        };
292        // SAFETY: old buffer holds `len` initialized Ts.
293        unsafe {
294            ptr::copy_nonoverlapping(self.ptr.as_ptr(), new_ptr.as_ptr(), self.len());
295        }
296        self.deallocate_old();
297        self.ptr = new_ptr;
298        self.cap = new_cap as u8;
299    }
300}
301
302// `Index<usize>` / `IndexMut<usize>` are reachable through `Deref<Target=[T]>` —
303// `[T]: Index<usize>` and autoderef makes `tv[i]` work. No need to implement them here.
304
305impl<T, const MAX: u8> std::ops::Deref for TinyVec<T, MAX> {
306    type Target = [T];
307    fn deref(&self) -> &[T] {
308        self.as_slice()
309    }
310}
311
312impl<T, const MAX: u8> std::ops::DerefMut for TinyVec<T, MAX> {
313    fn deref_mut(&mut self) -> &mut [T] {
314        self.as_mut_slice()
315    }
316}
317
318impl<T, const MAX: u8> TinyVec<T, MAX> {
319    /// Extend from an exact-sized iterator: reserves exactly once before the loop,
320    /// avoiding the `size_hint().0` lower-bound dance.
321    ///
322    /// All in-tree callers feed exact-sized iterators (typically `Vec::IntoIter` from
323    /// `TinyVec::into_iter`), so we expose this as the preferred API. The `Extend` trait
324    /// impl below stays for compatibility with generic code.
325    pub fn extend_exact<I>(&mut self, iter: I)
326    where
327        I: IntoIterator<Item = T>,
328        I::IntoIter: ExactSizeIterator,
329    {
330        let iter = iter.into_iter();
331        self.reserve(iter.len());
332        for item in iter {
333            self.push(item);
334        }
335    }
336}
337
338impl<T, const MAX: u8> IntoIterator for TinyVec<T, MAX> {
339    type Item = T;
340    type IntoIter = std::vec::IntoIter<T>;
341
342    fn into_iter(self) -> Self::IntoIter {
343        // Delegate to `Vec::IntoIter` rather than maintaining our own. ManuallyDrop the
344        // self so its Drop doesn't fire — the reconstructed Vec now owns the buffer.
345        let me = ManuallyDrop::new(self);
346        // SAFETY: by struct invariant, `(self.ptr, self.len, self.cap)` is a valid
347        // `Vec::from_raw_parts` triple.
348        unsafe { Vec::from_raw_parts(me.ptr.as_ptr(), me.len as usize, me.cap as usize) }
349            .into_iter()
350    }
351}
352
353// `for x in &tv` and `for x in &mut tv` require `&TinyVec` / `&mut TinyVec` to implement
354// `IntoIterator`. The `for` loop's desugaring doesn't apply `Deref` coercion across the
355// reference boundary, so we need these explicit impls. They're trivial — just dispatch to
356// the slice iterators reached through `Deref`.
357impl<'a, T, const MAX: u8> IntoIterator for &'a TinyVec<T, MAX> {
358    type Item = &'a T;
359    type IntoIter = std::slice::Iter<'a, T>;
360    fn into_iter(self) -> std::slice::Iter<'a, T> {
361        self.as_slice().iter()
362    }
363}
364
365impl<'a, T, const MAX: u8> IntoIterator for &'a mut TinyVec<T, MAX> {
366    type Item = &'a mut T;
367    type IntoIter = std::slice::IterMut<'a, T>;
368    fn into_iter(self) -> std::slice::IterMut<'a, T> {
369        self.as_mut_slice().iter_mut()
370    }
371}
372
373impl<T: fmt::Debug, const MAX: u8> fmt::Debug for TinyVec<T, MAX> {
374    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375        f.debug_list().entries(self.iter()).finish()
376    }
377}
378
379impl<T, const MAX: u8> Drop for TinyVec<T, MAX> {
380    #[inline]
381    fn drop(&mut self) {
382        // Fast path for empty containers: skip both the drop_in_place and deallocate calls.
383        // Hot because `TinyVec::default()` followed by immediate drop is a common idiom in
384        // benchmarks and in the steady-state of tasks that never allocate anything lazy.
385        if self.cap == 0 {
386            return;
387        }
388        // Drop populated elements in place.
389        if self.len > 0 {
390            // SAFETY: we own `len` initialized elements at the start of the buffer.
391            unsafe {
392                ptr::drop_in_place(std::ptr::slice_from_raw_parts_mut(
393                    self.ptr.as_ptr(),
394                    self.len(),
395                ));
396            }
397        }
398        self.deallocate_old();
399    }
400}
401
402impl<T, const MAX: u8> shrink_to_fit::ShrinkToFit for TinyVec<T, MAX> {
403    fn shrink_to_fit(&mut self) {
404        Self::shrink_to_fit(self);
405    }
406}
407
408#[cfg(test)]
409mod tests {
410    use super::*;
411
412    /// Test helper: build a TinyVec from an exact-sized iterator. Replaces the previous use
413    /// of `Iterator::collect()` after we removed the `FromIterator` impl.
414    fn from_exact<T, I, const MAX: u8>(iter: I) -> TinyVec<T, MAX>
415    where
416        I: IntoIterator<Item = T>,
417        I::IntoIter: ExactSizeIterator,
418    {
419        let mut v = TinyVec::new();
420        v.extend_exact(iter);
421        v
422    }
423
424    #[test]
425    fn size() {
426        // The whole point: 16 B on 64-bit, vs 24 B for Vec.
427        assert_eq!(std::mem::size_of::<TinyVec<u64>>(), 16);
428        assert_eq!(std::mem::size_of::<TinyVec<[u8; 48]>>(), 16);
429    }
430
431    #[test]
432    fn push_iter_swap_remove() {
433        let mut v: TinyVec<u32> = TinyVec::new();
434        assert!(v.is_empty());
435        v.push(10);
436        v.push(20);
437        v.push(30);
438        assert_eq!(v.len(), 3);
439        assert_eq!(v.iter().copied().collect::<Vec<_>>(), vec![10, 20, 30]);
440        let removed = v.swap_remove(0);
441        assert_eq!(removed, 10);
442        // After swap_remove(0), buffer is [30, 20] (last swapped into hole).
443        assert_eq!(v.iter().copied().collect::<Vec<_>>(), vec![30, 20]);
444        assert_eq!(v[0], 30);
445        assert_eq!(v[1], 20);
446    }
447
448    #[test]
449    fn growth_pattern() {
450        let mut v: TinyVec<u32> = TinyVec::new();
451        for i in 0..32u32 {
452            v.push(i);
453        }
454        assert_eq!(v.len(), 32);
455        let collected: Vec<u32> = v.iter().copied().collect();
456        assert_eq!(collected, (0..32).collect::<Vec<_>>());
457    }
458
459    #[test]
460    fn extend_and_reserve() {
461        let mut v: TinyVec<u32> = TinyVec::new();
462        v.extend_exact(0..10);
463        assert_eq!(v.len(), 10);
464        v.reserve(5);
465        assert!(v.capacity() >= 15);
466    }
467
468    #[test]
469    fn last_mut_and_index_mut() {
470        let mut v: TinyVec<u32> = TinyVec::new();
471        v.push(1);
472        v.push(2);
473        *v.last_mut().unwrap() = 99;
474        assert_eq!(v[1], 99);
475        v[0] = 7;
476        assert_eq!(v[0], 7);
477    }
478
479    #[test]
480    fn drop_runs_on_elements() {
481        use std::sync::atomic::{AtomicUsize, Ordering};
482
483        struct DropCounter<'a>(&'a AtomicUsize);
484        impl<'a> Drop for DropCounter<'a> {
485            fn drop(&mut self) {
486                self.0.fetch_add(1, Ordering::SeqCst);
487            }
488        }
489
490        let count = AtomicUsize::new(0);
491        {
492            let mut v: TinyVec<DropCounter<'_>> = TinyVec::new();
493            v.push(DropCounter(&count));
494            v.push(DropCounter(&count));
495            v.push(DropCounter(&count));
496        }
497        assert_eq!(count.load(Ordering::SeqCst), 3);
498    }
499
500    #[test]
501    fn into_iter_drops_and_yields() {
502        use std::sync::atomic::{AtomicUsize, Ordering};
503
504        struct DropCounter<'a>(&'a AtomicUsize, u32);
505        impl<'a> Drop for DropCounter<'a> {
506            fn drop(&mut self) {
507                self.0.fetch_add(1, Ordering::SeqCst);
508            }
509        }
510
511        let count = AtomicUsize::new(0);
512        let mut v: TinyVec<DropCounter<'_>> = TinyVec::new();
513        v.push(DropCounter(&count, 1));
514        v.push(DropCounter(&count, 2));
515        v.push(DropCounter(&count, 3));
516
517        let mut iter = v.into_iter();
518        assert_eq!(iter.next().unwrap().1, 1);
519        assert_eq!(iter.next().unwrap().1, 2);
520        // Drop iterator with one remaining element.
521        drop(iter);
522        // 3 total drops: the two yielded + the one remaining in the iter.
523        assert_eq!(count.load(Ordering::SeqCst), 3);
524    }
525
526    #[test]
527    fn shrink_to_fit_releases_buffer() {
528        let mut v: TinyVec<u32> = TinyVec::new();
529        v.extend_exact(0..10);
530        assert!(v.capacity() >= 10);
531        for _ in 0..10 {
532            v.swap_remove(0);
533        }
534        assert!(v.is_empty());
535        v.shrink_to_fit();
536        assert_eq!(v.capacity(), 0);
537    }
538
539    #[test]
540    #[should_panic(expected = "TinyVec capacity overflow")]
541    fn capacity_overflow_panics() {
542        let mut v: TinyVec<u8> = TinyVec::new();
543        for _ in 0..255u32 {
544            v.push(0);
545        }
546        // The 256th push trips the MAX check (default MAX = u8::MAX = 255).
547        v.push(0);
548    }
549
550    /// `MAX` strictly caps push count; growth stops at exactly MAX even when doubling would
551    /// overshoot.
552    #[test]
553    fn tight_max_caps_growth_exactly() {
554        let mut v: TinyVec<u32, 5> = TinyVec::new();
555        for i in 0..5 {
556            v.push(i);
557        }
558        assert_eq!(v.len(), 5);
559        // Capacity should be exactly 5, not the next-power-of-two (8).
560        assert_eq!(v.capacity(), 5);
561    }
562
563    #[test]
564    #[should_panic(expected = "TinyVec capacity overflow")]
565    fn tight_max_panics_at_limit() {
566        let mut v: TinyVec<u32, 3> = TinyVec::new();
567        v.push(0);
568        v.push(1);
569        v.push(2);
570        // The 4th push exceeds MAX=3.
571        v.push(3);
572    }
573
574    /// Confirms the growth schedule with tight MAX: doubles until it would exceed MAX, then
575    /// caps. With MAX=10 we should see 0 -> 4 -> 8 -> 10.
576    #[test]
577    fn tight_max_growth_schedule() {
578        let mut v: TinyVec<u32, 10> = TinyVec::new();
579        let mut last_cap = 0;
580        let mut cap_changes = Vec::new();
581        for i in 0..10 {
582            v.push(i);
583            if v.capacity() != last_cap {
584                cap_changes.push(v.capacity());
585                last_cap = v.capacity();
586            }
587        }
588        assert_eq!(cap_changes, vec![4, 8, 10]);
589    }
590
591    #[test]
592    fn retain_mut_basic() {
593        let mut v: TinyVec<u32> = from_exact(0..10);
594        v.retain_mut(|x| *x % 2 == 0);
595        assert_eq!(v.iter().copied().collect::<Vec<_>>(), vec![0, 2, 4, 6, 8]);
596        // retain_mut shouldn't change capacity.
597        assert!(v.capacity() >= 5);
598    }
599
600    #[test]
601    fn retain_mut_can_mutate() {
602        let mut v: TinyVec<u32> = from_exact(0..5);
603        v.retain_mut(|x| {
604            *x *= 10;
605            *x != 30
606        });
607        assert_eq!(v.iter().copied().collect::<Vec<_>>(), vec![0, 10, 20, 40]);
608    }
609
610    #[test]
611    fn retain_mut_empty() {
612        let mut v: TinyVec<u32> = TinyVec::new();
613        v.retain_mut(|_| panic!("should not be called for empty"));
614        assert!(v.is_empty());
615    }
616
617    #[test]
618    fn retain_mut_keeps_all() {
619        let mut v: TinyVec<u32> = from_exact(0..5);
620        v.retain_mut(|_| true);
621        assert_eq!(v.iter().copied().collect::<Vec<_>>(), vec![0, 1, 2, 3, 4]);
622    }
623
624    #[test]
625    fn retain_mut_removes_all() {
626        let mut v: TinyVec<u32> = from_exact(0..5);
627        v.retain_mut(|_| false);
628        assert!(v.is_empty());
629    }
630
631    /// Verifies retain_mut's panic guard: if the predicate panics, we shouldn't double-free.
632    #[test]
633    fn retain_mut_panic_safety() {
634        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
635            let mut v: TinyVec<u32> = from_exact(0..10);
636            v.retain_mut(|x| {
637                if *x == 5 {
638                    panic!("boom");
639                }
640                true
641            });
642        }));
643        assert!(result.is_err());
644    }
645
646    /// Element Drop panic during retain_mut — `Vec::retain_mut` handles this; we should too.
647    #[test]
648    fn retain_mut_element_drop_panic() {
649        use std::sync::atomic::{AtomicUsize, Ordering};
650
651        struct PanicyDrop<'a>(u32, &'a AtomicUsize);
652        impl Drop for PanicyDrop<'_> {
653            fn drop(&mut self) {
654                self.1.fetch_add(1, Ordering::SeqCst);
655                if self.0 == 5 && !std::thread::panicking() {
656                    panic!("boom from drop");
657                }
658            }
659        }
660
661        let drop_count = AtomicUsize::new(0);
662        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
663            let mut v: TinyVec<PanicyDrop<'_>> =
664                from_exact((0..10).map(|i| PanicyDrop(i, &drop_count)));
665            v.retain_mut(|x| x.0 != 5); // schedules drop of element with 0==5, which panics
666            // If we get here without panic, drop happened cleanly.
667        }));
668        // The panic should have propagated; some drops should have occurred.
669        assert!(result.is_err() || drop_count.load(Ordering::SeqCst) > 0);
670    }
671}