Skip to content
Merged
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
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
name = "thin-vec"
version = "0.2.19"
authors = ["Aria Beingessner <a.beingessner@gmail.com>"]
edition = "2018"
rust-version = "1.53"
edition = "2024"
rust-version = "1.85"
description = "A Vec that takes up less space on the stack."
readme = "README.md"
homepage = "https://github.com/mozilla/thin-vec"
Expand Down
10 changes: 10 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# Unreleased

* Make const_new the default, and bump MSRV to 1.85. `const_new` feature is still available but does nothing.

# Version 0.2.19 (2026-07-26)

* add may_dangle Drop impl under unstable feature
* Use safe `Layout::from_size_align` (instead of unsafe `from_size_align_unchecked`), panicking with a capacity overflow if it fails.
* Fix `shallow_size_of` for inline arrays.

# Versions 0.2.17 and 0.2.18 (2026-04-29)
* Fix compiling some feature combinations in no_std mode

Expand Down
132 changes: 55 additions & 77 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,6 @@
//!
//! # Optional Features
//!
//! ## `const_new`
//!
//! **This feature requires Rust 1.83.**
//!
//! This feature makes `ThinVec::new()` a `const fn`.
//!
//!
//! # Gecko FFI
//!
//! If you enable the gecko-ffi feature, `ThinVec` will verbatim bridge with the nsTArray type in
Expand Down Expand Up @@ -411,26 +404,24 @@ fn alloc_size<T>(cap: usize) -> usize {
}

/// Gets the padding necessary for the array of a `ThinVec<T>`
fn padding<T>() -> usize {
const fn padding<T>() -> usize {
let alloc_align = alloc_align::<T>();
let header_size = mem::size_of::<Header>();

if alloc_align > header_size {
if cfg!(feature = "gecko-ffi") {
panic!(
"nsTArray does not handle alignment above > {} correctly",
header_size
);
}
alloc_align - header_size
} else {
0
if cfg!(feature = "gecko-ffi") {
assert!(
header_size >= alloc_align,
"nsTArray does not handle alignment above the header size correctly",
);
}
alloc_align.saturating_sub(header_size)
}

/// Gets the align necessary to allocate a `ThinVec<T>`
fn alloc_align<T>() -> usize {
max(mem::align_of::<T>(), mem::align_of::<Header>())
const fn alloc_align<T>() -> usize {
if mem::align_of::<T>() > mem::align_of::<Header>() {
return mem::align_of::<T>();
}
mem::align_of::<Header>()
}

/// Gets the layout necessary to allocate a `ThinVec<T>`
Expand Down Expand Up @@ -527,16 +518,9 @@ impl<T> ThinVec<T> {
/// Creates a new empty ThinVec.
///
/// This will not allocate.
#[cfg(not(feature = "const_new"))]
pub fn new() -> ThinVec<T> {
ThinVec::with_capacity(0)
}

/// Creates a new empty ThinVec.
///
/// This will not allocate.
#[cfg(feature = "const_new")]
pub const fn new() -> ThinVec<T> {
// See the comment in with_capacity().
let _ = padding::<T>();
unsafe {
ThinVec {
ptr: NonNull::new_unchecked(&EMPTY_HEADER as *const Header as *mut Header),
Expand Down Expand Up @@ -599,7 +583,7 @@ impl<T> ThinVec<T> {
/// // Only true **without** the gecko-ffi feature!
/// // assert_eq!(vec_units.capacity(), usize::MAX);
/// ```
pub fn with_capacity(cap: usize) -> ThinVec<T> {
pub fn with_capacity(cap: usize) -> Self {
// `padding` contains ~static assertions against types that are
// incompatible with the current feature flags. We also call it to
// invoke these assertions when getting a pointer to the `ThinVec`
Expand All @@ -608,19 +592,12 @@ impl<T> ThinVec<T> {
// double panic. We duplicate the assertion here so that it is
// testable,
let _ = padding::<T>();

if cap == 0 {
unsafe {
ThinVec {
ptr: NonNull::new_unchecked(&EMPTY_HEADER as *const Header as *mut Header),
boo: PhantomData,
}
}
} else {
ThinVec {
ptr: header_with_capacity::<T>(cap, false),
boo: PhantomData,
}
return Self::new();
}
ThinVec {
ptr: header_with_capacity::<T>(cap, false),
boo: PhantomData,
}
}

Expand Down Expand Up @@ -678,7 +655,7 @@ impl<T> ThinVec<T> {

// This is unsafe when the header is EMPTY_HEADER.
unsafe fn header_mut(&mut self) -> &mut Header {
&mut *self.ptr()
unsafe { &mut *self.ptr() }
}

/// Returns the number of elements in the vector, also referred to
Expand Down Expand Up @@ -764,7 +741,7 @@ impl<T> ThinVec<T> {
/// # // don't use this as a starting point for a real library.
/// # pub struct StreamWrapper { strm: *mut std::ffi::c_void }
/// # const Z_OK: i32 = 0;
/// # extern "C" {
/// # unsafe extern "C" {
/// # fn deflateGetDictionary(
/// # strm: *mut std::ffi::c_void,
/// # dictionary: *mut u8,
Expand Down Expand Up @@ -820,13 +797,14 @@ impl<T> ThinVec<T> {
// less than or equal to capacity(). The same applies here.
debug_assert!(len == 0, "invalid set_len({}) on empty ThinVec", len);
} else {
self.header_mut().set_len(len)
unsafe { self.set_len_non_singleton(len) }
}
}

// For internal use only, when setting the length and it's known to be the non-singleton.
#[inline]
unsafe fn set_len_non_singleton(&mut self, len: usize) {
self.header_mut().set_len(len)
unsafe { self.header_mut().set_len(len) }
}

/// Appends an element to the back of a collection.
Expand Down Expand Up @@ -1149,11 +1127,7 @@ impl<T> ThinVec<T> {
// 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
if mem::size_of::<T>() > (!0) / 8 {
1
} else {
4
}
if mem::size_of::<T>() > (!0) / 8 { 1 } else { 4 }
} else {
old_cap.saturating_mul(2)
};
Expand Down Expand Up @@ -1747,17 +1721,18 @@ impl<T> ThinVec<T> {
debug_assert!(new_cap > 0);
if self.has_allocation() {
let old_cap = self.capacity();
let ptr = realloc(
self.ptr() as *mut u8,
layout::<T>(old_cap),
alloc_size::<T>(new_cap),
) as *mut Header;

if ptr.is_null() {
handle_alloc_error(layout::<T>(new_cap))
unsafe {
let ptr = realloc(
self.ptr() as *mut u8,
layout::<T>(old_cap),
alloc_size::<T>(new_cap),
) as *mut Header;
if ptr.is_null() {
handle_alloc_error(layout::<T>(new_cap))
}
(*ptr).set_cap_and_auto(new_cap, (*ptr).is_auto());
self.ptr = NonNull::new_unchecked(ptr);
}
(*ptr).set_cap_and_auto(new_cap, (*ptr).is_auto());
self.ptr = NonNull::new_unchecked(ptr);
} else {
let mut new_header = header_with_capacity::<T>(new_cap, self.is_auto_array());

Expand All @@ -1774,13 +1749,15 @@ impl<T> ThinVec<T> {
// by leaving behind a valid empty instance.
let len = self.len();
if cfg!(feature = "gecko-ffi") && len > 0 {
new_header
.as_ptr()
.add(1)
.cast::<T>()
.copy_from_nonoverlapping(self.data_raw(), len);
self.set_len_non_singleton(0);
new_header.as_mut().set_len(len);
unsafe {
new_header
.as_ptr()
.add(1)
.cast::<T>()
.copy_from_nonoverlapping(self.data_raw(), len);
self.set_len_non_singleton(0);
new_header.as_mut().set_len(len);
}
}

self.ptr = new_header;
Expand Down Expand Up @@ -2110,8 +2087,8 @@ impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for ThinVec<T> {
where
D: serde::Deserializer<'de>,
{
use serde::de::{SeqAccess, Visitor};
use serde::Deserialize;
use serde::de::{SeqAccess, Visitor};

struct ThinVecVisitor<T>(PhantomData<T>);

Expand Down Expand Up @@ -2925,7 +2902,7 @@ impl<T, const N: usize> AutoThinVec<T, N> {
let this = unsafe { self.get_unchecked_mut() };
this.buffer.header.set_len(0);
// TODO(emilio): Use NonNull::from_mut when msrv allows.
this.inner.ptr = NonNull::new_unchecked(&mut this.buffer.header);
this.inner.ptr = unsafe { NonNull::new_unchecked(&mut this.buffer.header) };
debug_assert!(this.inner.is_auto_array());
debug_assert!(this.inner.uses_stack_allocated_buffer());
}
Expand Down Expand Up @@ -2976,11 +2953,12 @@ impl<T> Drain<'_, T> {
};

for place in range_slice {
if let Some(new_item) = replace_with.next() {
unsafe { ptr::write(place, new_item) };
vec.set_len(vec.len() + 1);
} else {
let Some(new_item) = replace_with.next() else {
return false;
};
unsafe {
ptr::write(place, new_item);
vec.set_len(vec.len() + 1);
}
}
true
Expand Down Expand Up @@ -3102,7 +3080,7 @@ impl std::io::Write for ThinVec<u8> {

#[cfg(test)]
mod tests {
use super::{ThinVec, MAX_CAP};
use super::{MAX_CAP, ThinVec};
use crate::alloc::{string::ToString, vec};

#[test]
Expand Down Expand Up @@ -4736,7 +4714,7 @@ mod std_tests {
}

#[cfg(feature = "serde")]
use serde_test::{assert_tokens, Token};
use serde_test::{Token, assert_tokens};

#[test]
#[cfg(feature = "serde")]
Expand Down
Loading