From 2cbd6484064849543e0eec7224a6016a8337b3f5 Mon Sep 17 00:00:00 2001 From: Baptistemontan Date: Tue, 1 Sep 2026 16:23:07 +0200 Subject: [PATCH 01/10] don't allocate for ZSTs --- src/lib.rs | 221 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 182 insertions(+), 39 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 15f230c..457e6d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -174,7 +174,9 @@ use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps}; #[cfg(not(feature = "gecko-ffi"))] mod impl_details { pub type SizeType = usize; - pub const MAX_CAP: usize = !0; + // for ZSTs, store the length in the the NonNull as a NonZero, + // the length is thus off by one and can only reach usize::MAX - 1 + pub const MAX_CAP: usize = usize::MAX - 1; #[inline(always)] pub fn assert_size(x: usize) -> SizeType { @@ -471,6 +473,13 @@ fn header_with_capacity(cap: usize, is_auto: bool) -> NonNull
{ } } +/// Safety: len must be != 0 +unsafe fn len_to_ptr_unchecked(len: usize) -> NonNull { + use std::num::NonZeroUsize; + // NonZero::without_provenance polyfill + unsafe { mem::transmute(NonZeroUsize::new_unchecked(len)) } +} + /// See the crate's top level documentation for a description of this type. #[repr(C)] pub struct ThinVec { @@ -519,6 +528,11 @@ macro_rules! thin_vec { } impl ThinVec { + /// Return true if we can use ZST optimizations + const fn is_zst() -> bool { + size_of::() == 0 && !cfg!(feature = "gecko-ffi") + } + /// Creates a new empty ThinVec. /// /// This will not allocate. @@ -585,7 +599,7 @@ impl ThinVec { /// // space is needed to store the actual elements. /// // Note this is only true **without** the gecko-ffi feature! /// let vec_units = ThinVec::<()>::with_capacity(10); - /// assert_eq!(vec_units.capacity(), usize::MAX); + /// assert_eq!(vec_units.capacity(), usize::MAX - 1); /// # } /// ``` pub fn with_capacity(cap: usize) -> Self { @@ -596,6 +610,16 @@ impl ThinVec { // `Drop` impl, trippng an assertion along that code path causes a // double panic. We duplicate the assertion here so that it is // testable, + + if Self::is_zst() { + unsafe { + return ThinVec { + ptr: len_to_ptr_unchecked(1), + boo: PhantomData, + }; + } + } + let _ = padding::(); if cap == 0 { return Self::new(); @@ -608,13 +632,26 @@ impl ThinVec { // Accessor conveniences - fn ptr(&self) -> *mut Header { + /// # Safety + /// Self::is_zst() == false + unsafe fn ptr(&self) -> *mut Header { + debug_assert!(!Self::is_zst()); self.ptr.as_ptr() } - fn header(&self) -> &Header { + + /// # Safety + /// Self::is_zst() == false + unsafe fn header(&self) -> &Header { + debug_assert!(!Self::is_zst()); unsafe { self.ptr.as_ref() } } + fn data_raw(&self) -> *mut T { + if Self::is_zst() { + // Polyfill for ptr::dangling_mut(), stable from 1.84 + return NonNull::dangling().as_ptr(); + } + // `padding` contains ~static assertions against types that are // incompatible with the current feature flags. Even if we don't // care about its result, we should always call it before getting @@ -658,8 +695,10 @@ impl ThinVec { } } - // This is unsafe when the header is EMPTY_HEADER. + /// # Safety + /// This is unsafe when the header is EMPTY_HEADER or when T is a ZST. unsafe fn header_mut(&mut self) -> &mut Header { + debug_assert!(!Self::is_zst()); unsafe { &mut *self.ptr() } } @@ -675,7 +714,11 @@ impl ThinVec { /// assert_eq!(a.len(), 3); /// ``` pub fn len(&self) -> usize { - self.header().len() + if Self::is_zst() { + (self.ptr.as_ptr() as usize) - 1 + } else { + unsafe { self.header().len() } + } } /// Returns `true` if the vector contains no elements. @@ -707,7 +750,11 @@ impl ThinVec { /// assert_eq!(vec.capacity(), 10); /// ``` pub fn capacity(&self) -> usize { - self.header().cap() + if Self::is_zst() { + MAX_CAP + } else { + unsafe { self.header().cap() } + } } /// Returns `true` if the vector has the capacity to hold any element. @@ -797,7 +844,10 @@ impl ThinVec { /// Normally, here, one would use [`clear`] instead to correctly drop /// the contents and thus not leak memory. pub unsafe fn set_len(&mut self, len: usize) { - if self.is_singleton() { + if Self::is_zst() { + // since self.cap() return usize::MAX - 1 it's the caller reponsability to ensure len is < usize::MAX + unsafe { self.set_len_zst(len) }; + } else if self.is_singleton() { // A prerequisite of `Vec::set_len` is that `new_len` must be // less than or equal to capacity(). The same applies here. debug_assert!(len == 0, "invalid set_len({}) on empty ThinVec", len); @@ -806,12 +856,41 @@ impl ThinVec { } } - // For internal use only, when setting the length and it's known to be the non-singleton. + /// For internal use only, when setting the length and it's known that T is a ZST. + /// # Safety + /// - This is unsafe when T is not a ZST. + /// - len must be < usize::MAX #[inline] - unsafe fn set_len_non_singleton(&mut self, len: usize) { + unsafe fn set_len_zst(&mut self, len: usize) { + debug_assert!( + len <= MAX_CAP, + "invalid set_len(usize::MAX) on ZST ThinVec (max cap is usize::MAX - 1)" + ); + unsafe { self.ptr = len_to_ptr_unchecked(len + 1) } + } + + /// For internal use only, when setting the length and it's known that the header is owned. + /// # Safety + /// This is unsafe when the header is EMPTY_HEADER or when T is a ZST. + #[inline] + unsafe fn set_header_len(&mut self, len: usize) { unsafe { self.header_mut().set_len(len) } } + /// For internal use only, when setting the length and it's known to be the non-singleton. + /// # Safety + /// This is unsafe when the header is EMPTY_HEADER. + #[inline(always)] + unsafe fn set_len_non_singleton(&mut self, len: usize) { + if Self::is_zst() { + unsafe { + self.set_len_zst(len); + } + } else { + unsafe { self.set_header_len(len) } + } + } + /// Appends an element to the back of a collection. /// /// # Panics @@ -829,7 +908,9 @@ impl ThinVec { /// ``` pub fn push(&mut self, val: T) { let old_len = self.len(); - if old_len == self.capacity() { + if Self::is_zst() { + assert!(old_len < MAX_CAP); + } else if old_len == self.capacity() { self.reserve(1); } unsafe { @@ -850,9 +931,12 @@ impl ThinVec { let old_len = self.len(); debug_assert!(old_len < self.capacity()); unsafe { - ptr::write(self.data_raw().add(old_len), val); - - // SAFETY: capacity > len >= 0, so capacity != 0, so this is not a singleton. + if Self::is_zst() { + mem::forget(val); + } else { + ptr::write(self.data_raw().add(old_len), val); + // SAFETY: capacity > len >= 0, so capacity != 0, so this is not a singleton. + } self.set_len_non_singleton(old_len + 1); } } @@ -877,7 +961,11 @@ impl ThinVec { unsafe { self.set_len_non_singleton(old_len - 1); - Some(ptr::read(self.data_raw().add(old_len - 1))) + if Self::is_zst() { + Some(mem::zeroed()) + } else { + Some(ptr::read(self.data_raw().add(old_len - 1))) + } } } @@ -903,6 +991,14 @@ impl ThinVec { let old_len = self.len(); assert!(idx <= old_len, "Index out of bounds"); + if Self::is_zst() { + assert!(old_len < MAX_CAP); + mem::forget(elem); + unsafe { + self.set_len_zst(old_len + 1); + } + return; + } if old_len == self.capacity() { self.reserve(1); } @@ -910,7 +1006,7 @@ impl ThinVec { let ptr = self.data_raw(); ptr::copy(ptr.add(idx), ptr.add(idx + 1), old_len - idx); ptr::write(ptr.add(idx), elem); - self.set_len_non_singleton(old_len + 1); + self.set_header_len(old_len + 1); } } @@ -944,10 +1040,14 @@ impl ThinVec { unsafe { self.set_len_non_singleton(old_len - 1); - let ptr = self.data_raw(); - let val = ptr::read(self.data_raw().add(idx)); - ptr::copy(ptr.add(idx + 1), ptr.add(idx), old_len - idx - 1); - val + if Self::is_zst() { + mem::zeroed() + } else { + let ptr = self.data_raw(); + let val = ptr::read(self.data_raw().add(idx)); + ptr::copy(ptr.add(idx + 1), ptr.add(idx), old_len - idx - 1); + val + } } } @@ -983,10 +1083,15 @@ impl ThinVec { assert!(idx < old_len, "Index out of bounds"); unsafe { - let ptr = self.data_raw(); - ptr::swap(ptr.add(idx), ptr.add(old_len - 1)); - self.set_len_non_singleton(old_len - 1); - ptr::read(ptr.add(old_len - 1)) + if Self::is_zst() { + self.set_len_zst(old_len - 1); + mem::zeroed() + } else { + let ptr = self.data_raw(); + ptr::swap(ptr.add(idx), ptr.add(old_len - 1)); + self.set_header_len(old_len - 1); + ptr::read(ptr.add(old_len - 1)) + } } } @@ -1046,7 +1151,12 @@ impl ThinVec { // doesn't re-drop the just-failed value. let new_len = self.len() - 1; self.set_len_non_singleton(new_len); - ptr::drop_in_place(self.data_raw().add(new_len)); + let ptr = if Self::is_zst() { + NonNull::dangling().as_ptr() + } else { + self.data_raw().add(new_len) + }; + ptr::drop_in_place(ptr); } } } @@ -1129,6 +1239,10 @@ impl ThinVec { if min_cap <= old_cap { return; } + // only way to get here is if min_cap == usize::MAX, which we can't handle. + if Self::is_zst() { + capacity_overflow(); + } // Ensure the new capacity is at least double, to guarantee exponential growth. let double_cap = if old_cap == 0 { // skip to 4 because tiny ThinVecs are dumb; but not if that would cause overflow @@ -1156,7 +1270,6 @@ impl ThinVec { if min_cap <= old_cap { return; } - // The growth logic can't handle zero-sized types, so we have to exit // early here. if elem_size == 0 { @@ -1209,6 +1322,9 @@ impl ThinVec { let new_cap = self.len().checked_add(additional).unwrap_cap_overflow(); let old_cap = self.capacity(); if new_cap > old_cap { + if Self::is_zst() { + capacity_overflow() + } unsafe { self.reallocate(new_cap); } @@ -1232,6 +1348,9 @@ impl ThinVec { /// assert!(vec.capacity() >= 3); /// ``` pub fn shrink_to_fit(&mut self) { + if Self::is_zst() { + return; + } let old_cap = self.capacity(); let new_cap = self.len(); if new_cap >= old_cap { @@ -1722,8 +1841,10 @@ impl ThinVec { /// Resize the buffer and update its capacity, without changing the length. /// Unsafe because it can cause length to be greater than capacity. + /// Must not be called if Self::is_zst() unsafe fn reallocate(&mut self, new_cap: usize) { debug_assert!(new_cap > 0); + debug_assert!(!Self::is_zst()); if self.has_allocation() { let old_cap = self.capacity(); unsafe { @@ -1760,7 +1881,7 @@ impl ThinVec { .add(1) .cast::() .copy_from_nonoverlapping(self.data_raw(), len); - self.set_len_non_singleton(0); + self.set_header_len(0); new_header.as_mut().set_len(len); } } @@ -1772,7 +1893,13 @@ impl ThinVec { #[inline] #[allow(unused_unsafe)] fn is_singleton(&self) -> bool { - unsafe { self.ptr.as_ptr() as *const Header == &EMPTY_HEADER } + // could technicaly remove this branch + // but there is a 1/2^64 chance of the number of ZST being equal to &EMPTY_HEADER + if Self::is_zst() { + false + } else { + unsafe { self.ptr.as_ptr() as *const Header == &EMPTY_HEADER } + } } #[cfg(feature = "gecko-ffi")] @@ -1903,6 +2030,14 @@ impl ThinVec { } } +#[cold] +#[inline(never)] +fn drop_zsts(this: &mut ThinVec) { + unsafe { + ptr::drop_in_place(&mut this[..]); + } +} + #[cold] #[inline(never)] fn drop_non_singleton(this: &mut ThinVec) { @@ -1922,7 +2057,11 @@ impl Drop for ThinVec { #[inline] fn drop(&mut self) { if !self.is_singleton() { - drop_non_singleton(self); + if Self::is_zst() { + drop_zsts(self); + } else { + drop_non_singleton(self); + } } } } @@ -1932,7 +2071,11 @@ unsafe impl<#[may_dangle] T> Drop for ThinVec { #[inline] fn drop(&mut self) { if !self.is_singleton() { - drop_non_singleton(self); + if Self::is_zst() { + drop_zsts(self); + } else { + drop_non_singleton(self); + } } } } @@ -2128,7 +2271,7 @@ impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for ThinVec { #[cfg(feature = "malloc_size_of")] impl MallocShallowSizeOf for ThinVec { fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if self.capacity() == 0 || self.uses_stack_allocated_buffer() { + if self.capacity() == 0 || self.uses_stack_allocated_buffer() || Self::is_zst() { // We're not a heap pointer. return 0; } @@ -4098,19 +4241,19 @@ mod std_tests { #[test] #[cfg(not(feature = "gecko-ffi"))] fn test_drain_max_vec_size() { - let mut v = ThinVec::<()>::with_capacity(usize::MAX); + let mut v = ThinVec::<()>::with_capacity(MAX_CAP); unsafe { - v.set_len(usize::MAX); + v.set_len(MAX_CAP); } - for _ in v.drain(usize::MAX - 1..) {} - assert_eq!(v.len(), usize::MAX - 1); + for _ in v.drain(MAX_CAP - 1..) {} + assert_eq!(v.len(), MAX_CAP - 1); - let mut v = ThinVec::<()>::with_capacity(usize::MAX); + let mut v = ThinVec::<()>::with_capacity(MAX_CAP); unsafe { - v.set_len(usize::MAX); + v.set_len(MAX_CAP); } - for _ in v.drain(usize::MAX - 1..=usize::MAX - 1) {} - assert_eq!(v.len(), usize::MAX - 1); + for _ in v.drain(MAX_CAP - 1..=MAX_CAP - 1) {} + assert_eq!(v.len(), MAX_CAP - 1); } #[test] From 011927e5d5656db87e67c78a43f9258f63b6d64d Mon Sep 17 00:00:00 2001 From: Baptistemontan Date: Tue, 1 Sep 2026 16:24:55 +0200 Subject: [PATCH 02/10] always inline is_zst() --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 457e6d2..72ec34e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -529,6 +529,7 @@ macro_rules! thin_vec { impl ThinVec { /// Return true if we can use ZST optimizations + #[inline(always)] const fn is_zst() -> bool { size_of::() == 0 && !cfg!(feature = "gecko-ffi") } From 38417847eca2973d40a50893518c307f0748d2bd Mon Sep 17 00:00:00 2001 From: Baptistemontan Date: Tue, 1 Sep 2026 16:47:16 +0200 Subject: [PATCH 03/10] fix typo --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 72ec34e..49ab299 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -476,7 +476,7 @@ fn header_with_capacity(cap: usize, is_auto: bool) -> NonNull
{ /// Safety: len must be != 0 unsafe fn len_to_ptr_unchecked(len: usize) -> NonNull { use std::num::NonZeroUsize; - // NonZero::without_provenance polyfill + // NonNull::without_provenance polyfill unsafe { mem::transmute(NonZeroUsize::new_unchecked(len)) } } From 1059bc60abebba8943f9ce83184cfd698eb9d6ad Mon Sep 17 00:00:00 2001 From: Baptistemontan Date: Tue, 1 Sep 2026 17:08:05 +0200 Subject: [PATCH 04/10] fix importing NonZeroUsize from std and import from core instead --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 49ab299..fa10cf4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -475,7 +475,7 @@ fn header_with_capacity(cap: usize, is_auto: bool) -> NonNull
{ /// Safety: len must be != 0 unsafe fn len_to_ptr_unchecked(len: usize) -> NonNull { - use std::num::NonZeroUsize; + use core::num::NonZeroUsize; // NonNull::without_provenance polyfill unsafe { mem::transmute(NonZeroUsize::new_unchecked(len)) } } From 16221cc4acae16e95de6d892cb2fed8e307adaa2 Mon Sep 17 00:00:00 2001 From: Baptistemontan Date: Tue, 15 Sep 2026 19:07:40 +0200 Subject: [PATCH 05/10] has_allocation now return false on ZSTs, and inlined the ZST drop_in_place inside the drop impl --- src/lib.rs | 83 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fa10cf4..f395147 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -473,8 +473,11 @@ fn header_with_capacity(cap: usize, is_auto: bool) -> NonNull
{ } } -/// Safety: len must be != 0 -unsafe fn len_to_ptr_unchecked(len: usize) -> NonNull { +/// # Safety +/// +/// len must be != 0, this uses the `NonNull` to store a length, so the length must be stored offset by one. +/// This function expect the len to be already shifted +const unsafe fn len_to_ptr_unchecked(len: usize) -> NonNull { use core::num::NonZeroUsize; // NonNull::without_provenance polyfill unsafe { mem::transmute(NonZeroUsize::new_unchecked(len)) } @@ -538,12 +541,21 @@ impl ThinVec { /// /// This will not allocate. pub const fn new() -> ThinVec { - // See the comment in with_capacity(). - let _ = padding::(); - unsafe { - ThinVec { - ptr: NonNull::new_unchecked(&EMPTY_HEADER as *const Header as *mut Header), - boo: PhantomData, + if Self::is_zst() { + unsafe { + ThinVec { + ptr: len_to_ptr_unchecked(1), + boo: PhantomData, + } + } + } else { + // See the comment in with_capacity(). + let _ = padding::(); + unsafe { + ThinVec { + ptr: NonNull::new_unchecked(&EMPTY_HEADER as *const Header as *mut Header), + boo: PhantomData, + } } } } @@ -634,14 +646,16 @@ impl ThinVec { // Accessor conveniences /// # Safety - /// Self::is_zst() == false + /// + /// must have Self::is_zst() == false unsafe fn ptr(&self) -> *mut Header { debug_assert!(!Self::is_zst()); self.ptr.as_ptr() } /// # Safety - /// Self::is_zst() == false + /// + /// must have Self::is_zst() == false unsafe fn header(&self) -> &Header { debug_assert!(!Self::is_zst()); unsafe { self.ptr.as_ref() } @@ -697,8 +711,10 @@ impl ThinVec { } /// # Safety + /// /// This is unsafe when the header is EMPTY_HEADER or when T is a ZST. unsafe fn header_mut(&mut self) -> &mut Header { + debug_assert!(!self.is_singleton()); debug_assert!(!Self::is_zst()); unsafe { &mut *self.ptr() } } @@ -863,6 +879,7 @@ impl ThinVec { /// - len must be < usize::MAX #[inline] unsafe fn set_len_zst(&mut self, len: usize) { + debug_assert!(Self::is_zst()); debug_assert!( len <= MAX_CAP, "invalid set_len(usize::MAX) on ZST ThinVec (max cap is usize::MAX - 1)" @@ -878,7 +895,7 @@ impl ThinVec { unsafe { self.header_mut().set_len(len) } } - /// For internal use only, when setting the length and it's known to be the non-singleton. + /// For internal use only, when setting the length and it's known to be the non-singleton or T is a ZST. /// # Safety /// This is unsafe when the header is EMPTY_HEADER. #[inline(always)] @@ -1842,6 +1859,9 @@ impl ThinVec { /// Resize the buffer and update its capacity, without changing the length. /// Unsafe because it can cause length to be greater than capacity. + /// + /// # Safety + /// /// Must not be called if Self::is_zst() unsafe fn reallocate(&mut self, new_cap: usize) { debug_assert!(new_cap > 0); @@ -1936,7 +1956,7 @@ impl ThinVec { #[inline] fn has_allocation(&self) -> bool { - !self.is_singleton() && !self.uses_stack_allocated_buffer() + !Self::is_zst() && !self.is_singleton() && !self.uses_stack_allocated_buffer() } } @@ -2031,14 +2051,6 @@ impl ThinVec { } } -#[cold] -#[inline(never)] -fn drop_zsts(this: &mut ThinVec) { - unsafe { - ptr::drop_in_place(&mut this[..]); - } -} - #[cold] #[inline(never)] fn drop_non_singleton(this: &mut ThinVec) { @@ -2053,16 +2065,27 @@ fn drop_non_singleton(this: &mut ThinVec) { } } +/// # Safety +/// +/// This function drop and deallocates the inner values of the `ThinVec`, +/// invariants are therefore brokens and the value must be considered dropped and should not be accessed again. +#[inline] +unsafe fn drop_thin_vec(this: &mut ThinVec) { + if ThinVec::::is_zst() { + unsafe { + ptr::drop_in_place(&mut this[..]); + } + } else if !this.is_singleton() { + drop_non_singleton(this); + } +} + #[cfg(not(feature = "unstable"))] impl Drop for ThinVec { #[inline] fn drop(&mut self) { - if !self.is_singleton() { - if Self::is_zst() { - drop_zsts(self); - } else { - drop_non_singleton(self); - } + unsafe { + drop_thin_vec(self); } } } @@ -2071,12 +2094,8 @@ impl Drop for ThinVec { unsafe impl<#[may_dangle] T> Drop for ThinVec { #[inline] fn drop(&mut self) { - if !self.is_singleton() { - if Self::is_zst() { - drop_zsts(self); - } else { - drop_non_singleton(self); - } + unsafe { + drop_thin_vec(self); } } } From 34680ec70061695343c61ff2ad410954adc149a8 Mon Sep 17 00:00:00 2001 From: Baptistemontan Date: Tue, 15 Sep 2026 19:17:57 +0200 Subject: [PATCH 06/10] made `len_to_ptr_unchecked` const for the const_new feature --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index f395147..2e0d3f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -477,6 +477,7 @@ fn header_with_capacity(cap: usize, is_auto: bool) -> NonNull
{ /// /// len must be != 0, this uses the `NonNull` to store a length, so the length must be stored offset by one. /// This function expect the len to be already shifted +#[inline(always)] const unsafe fn len_to_ptr_unchecked(len: usize) -> NonNull { use core::num::NonZeroUsize; // NonNull::without_provenance polyfill From 18f14e1bb887c77b96a334cd142a7181ab816ac0 Mon Sep 17 00:00:00 2001 From: Baptistemontan Date: Tue, 15 Sep 2026 19:49:25 +0200 Subject: [PATCH 07/10] removed polyfill for ptr::dangling_mut --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2e0d3f9..d6483e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -664,8 +664,7 @@ impl ThinVec { fn data_raw(&self) -> *mut T { if Self::is_zst() { - // Polyfill for ptr::dangling_mut(), stable from 1.84 - return NonNull::dangling().as_ptr(); + return ptr::dangling_mut(); } // `padding` contains ~static assertions against types that are From c18315eee2f937ceda294439f6e3bc26b7304bdd Mon Sep 17 00:00:00 2001 From: Baptistemontan Date: Tue, 15 Sep 2026 20:51:51 +0200 Subject: [PATCH 08/10] fix typo --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index d6483e7..3f7acd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -862,7 +862,7 @@ impl ThinVec { /// the contents and thus not leak memory. pub unsafe fn set_len(&mut self, len: usize) { if Self::is_zst() { - // since self.cap() return usize::MAX - 1 it's the caller reponsability to ensure len is < usize::MAX + // since self.cap() returns usize::MAX - 1 it's the caller reponsability to ensure len is < usize::MAX unsafe { self.set_len_zst(len) }; } else if self.is_singleton() { // A prerequisite of `Vec::set_len` is that `new_len` must be From 66db9d6f70a43f92c4b0cdc6b5d9c245dc56937d Mon Sep 17 00:00:00 2001 From: Baptistemontan Date: Tue, 15 Sep 2026 21:18:08 +0200 Subject: [PATCH 09/10] remove unecessary zst checks --- src/lib.rs | 72 ++++++++++++++++-------------------------------------- 1 file changed, 21 insertions(+), 51 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3f7acd1..351eb19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -542,6 +542,9 @@ impl ThinVec { /// /// This will not allocate. pub const fn new() -> ThinVec { + // See the comment in with_capacity(). + let _ = padding::(); + if Self::is_zst() { unsafe { ThinVec { @@ -550,8 +553,6 @@ impl ThinVec { } } } else { - // See the comment in with_capacity(). - let _ = padding::(); unsafe { ThinVec { ptr: NonNull::new_unchecked(&EMPTY_HEADER as *const Header as *mut Header), @@ -625,6 +626,8 @@ impl ThinVec { // double panic. We duplicate the assertion here so that it is // testable, + let _ = padding::(); + if Self::is_zst() { unsafe { return ThinVec { @@ -634,7 +637,6 @@ impl ThinVec { } } - let _ = padding::(); if cap == 0 { return Self::new(); } @@ -861,10 +863,7 @@ impl ThinVec { /// Normally, here, one would use [`clear`] instead to correctly drop /// the contents and thus not leak memory. pub unsafe fn set_len(&mut self, len: usize) { - if Self::is_zst() { - // since self.cap() returns usize::MAX - 1 it's the caller reponsability to ensure len is < usize::MAX - unsafe { self.set_len_zst(len) }; - } else if self.is_singleton() { + if self.is_singleton() { // A prerequisite of `Vec::set_len` is that `new_len` must be // less than or equal to capacity(). The same applies here. debug_assert!(len == 0, "invalid set_len({}) on empty ThinVec", len); @@ -900,6 +899,7 @@ impl ThinVec { /// This is unsafe when the header is EMPTY_HEADER. #[inline(always)] unsafe fn set_len_non_singleton(&mut self, len: usize) { + debug_assert!(!self.is_singleton()); if Self::is_zst() { unsafe { self.set_len_zst(len); @@ -926,9 +926,7 @@ impl ThinVec { /// ``` pub fn push(&mut self, val: T) { let old_len = self.len(); - if Self::is_zst() { - assert!(old_len < MAX_CAP); - } else if old_len == self.capacity() { + if old_len == self.capacity() { self.reserve(1); } unsafe { @@ -949,12 +947,8 @@ impl ThinVec { let old_len = self.len(); debug_assert!(old_len < self.capacity()); unsafe { - if Self::is_zst() { - mem::forget(val); - } else { - ptr::write(self.data_raw().add(old_len), val); - // SAFETY: capacity > len >= 0, so capacity != 0, so this is not a singleton. - } + ptr::write(self.data_raw().add(old_len), val); + // SAFETY: capacity > len >= 0, so capacity != 0, so this is not a singleton. self.set_len_non_singleton(old_len + 1); } } @@ -979,11 +973,7 @@ impl ThinVec { unsafe { self.set_len_non_singleton(old_len - 1); - if Self::is_zst() { - Some(mem::zeroed()) - } else { - Some(ptr::read(self.data_raw().add(old_len - 1))) - } + Some(ptr::read(self.data_raw().add(old_len - 1))) } } @@ -1009,14 +999,6 @@ impl ThinVec { let old_len = self.len(); assert!(idx <= old_len, "Index out of bounds"); - if Self::is_zst() { - assert!(old_len < MAX_CAP); - mem::forget(elem); - unsafe { - self.set_len_zst(old_len + 1); - } - return; - } if old_len == self.capacity() { self.reserve(1); } @@ -1058,14 +1040,10 @@ impl ThinVec { unsafe { self.set_len_non_singleton(old_len - 1); - if Self::is_zst() { - mem::zeroed() - } else { - let ptr = self.data_raw(); - let val = ptr::read(self.data_raw().add(idx)); - ptr::copy(ptr.add(idx + 1), ptr.add(idx), old_len - idx - 1); - val - } + let ptr = self.data_raw(); + let val = ptr::read(self.data_raw().add(idx)); + ptr::copy(ptr.add(idx + 1), ptr.add(idx), old_len - idx - 1); + val } } @@ -1101,15 +1079,10 @@ impl ThinVec { assert!(idx < old_len, "Index out of bounds"); unsafe { - if Self::is_zst() { - self.set_len_zst(old_len - 1); - mem::zeroed() - } else { - let ptr = self.data_raw(); - ptr::swap(ptr.add(idx), ptr.add(old_len - 1)); - self.set_header_len(old_len - 1); - ptr::read(ptr.add(old_len - 1)) - } + let ptr = self.data_raw(); + ptr::swap(ptr.add(idx), ptr.add(old_len - 1)); + self.set_len_non_singleton(old_len - 1); + ptr::read(ptr.add(old_len - 1)) } } @@ -1169,11 +1142,7 @@ impl ThinVec { // doesn't re-drop the just-failed value. let new_len = self.len() - 1; self.set_len_non_singleton(new_len); - let ptr = if Self::is_zst() { - NonNull::dangling().as_ptr() - } else { - self.data_raw().add(new_len) - }; + let ptr = self.data_raw().add(new_len); ptr::drop_in_place(ptr); } } @@ -1340,6 +1309,7 @@ impl ThinVec { let new_cap = self.len().checked_add(additional).unwrap_cap_overflow(); let old_cap = self.capacity(); if new_cap > old_cap { + // only way to get here is if new_cap == usize::MAX, which we can't handle. if Self::is_zst() { capacity_overflow() } From 127a785850401dca8db9d3fd40160f4bc952bd92 Mon Sep 17 00:00:00 2001 From: Baptistemontan Date: Wed, 16 Sep 2026 16:16:21 +0200 Subject: [PATCH 10/10] minor changes --- src/lib.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 351eb19..69d5e16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -480,6 +480,7 @@ fn header_with_capacity(cap: usize, is_auto: bool) -> NonNull
{ #[inline(always)] const unsafe fn len_to_ptr_unchecked(len: usize) -> NonNull { use core::num::NonZeroUsize; + debug_assert!(len != 0); // NonNull::without_provenance polyfill unsafe { mem::transmute(NonZeroUsize::new_unchecked(len)) } } @@ -622,10 +623,9 @@ impl ThinVec { // incompatible with the current feature flags. We also call it to // invoke these assertions when getting a pointer to the `ThinVec` // contents, but since we also get a pointer to the contents in the - // `Drop` impl, trippng an assertion along that code path causes a + // `Drop` impl, tripping an assertion along that code path causes a // double panic. We duplicate the assertion here so that it is // testable, - let _ = padding::(); if Self::is_zst() { @@ -1884,8 +1884,6 @@ impl ThinVec { #[inline] #[allow(unused_unsafe)] fn is_singleton(&self) -> bool { - // could technicaly remove this branch - // but there is a 1/2^64 chance of the number of ZST being equal to &EMPTY_HEADER if Self::is_zst() { false } else { @@ -2038,7 +2036,7 @@ fn drop_non_singleton(this: &mut ThinVec) { /// # Safety /// /// This function drop and deallocates the inner values of the `ThinVec`, -/// invariants are therefore brokens and the value must be considered dropped and should not be accessed again. +/// invariants are therefore broken and the value must be considered dropped and should not be accessed again. #[inline] unsafe fn drop_thin_vec(this: &mut ThinVec) { if ThinVec::::is_zst() { @@ -2261,7 +2259,7 @@ impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for ThinVec { #[cfg(feature = "malloc_size_of")] impl MallocShallowSizeOf for ThinVec { fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if self.capacity() == 0 || self.uses_stack_allocated_buffer() || Self::is_zst() { + if !self.has_allocation() { // We're not a heap pointer. return 0; }