From 599aa0465801d6c5931acddeece121a2bdad406c Mon Sep 17 00:00:00 2001 From: Harsh Raj Singhania Date: Sun, 4 Oct 2026 21:12:06 +0530 Subject: [PATCH] Preserve the allocator in From for Box<[T], A> The previous conversion rebuilt the box through Vec, which always uses the global allocator. With the allocator API enabled, convert into Box<[T], A> and release spilled storage with the same allocator. Fixes #714 Signed-off-by: Harsh Raj Singhania --- src/conversions.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 ++- tests/main.rs | 18 +++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/conversions.rs b/src/conversions.rs index 13cde585..bf71ad8d 100644 --- a/src/conversions.rs +++ b/src/conversions.rs @@ -135,8 +135,89 @@ impl From> for Vec { } } +// Without the allocator API, `Box` is always `Global`. The conversion still +// has to move elements out of inline storage; heap storage is rebuilt by +// `Vec`, which is also `Global`. +#[cfg(not(feature = "allocator-api"))] impl From> for Box<[T]> { fn from(this: SmallVec) -> Self { Vec::from(this).into_boxed_slice() } } + +// Preserve `A` instead of rebuilding through `Vec` (which is `Global`). +// Elements are copied into a new allocation owned by `A`. Spilled storage is +// then released with that same allocator, so the original buffer is not left +// to `Vec`'s global deallocator. +#[cfg(all(feature = "allocator-api", not(feature = "allocator-api2")))] +impl From> for Box<[T], A> +where A: Allocator + core::alloc::Allocator +{ + fn from(this: SmallVec) -> Self { + into_boxed_with_allocator(this) + } +} + +#[cfg(feature = "allocator-api2")] +impl From> for Box<[T], A> +where A: Allocator + allocator_api2::alloc::Allocator +{ + fn from(this: SmallVec) -> Self { + into_boxed_with_allocator(this) + } +} + +#[cfg(feature = "allocator-api2")] +use allocator_api2::alloc::Allocator as BoxAlloc; +#[cfg(all(feature = "allocator-api", not(feature = "allocator-api2")))] +use core::alloc::Allocator as BoxAlloc; + +#[cfg(feature = "allocator-api")] +fn into_boxed_with_allocator(this: SmallVec) -> Box<[T], A> +where A: Allocator + BoxAlloc { + use core::{ + alloc::Layout, + mem::{ + MaybeUninit, + size_of + }, + ptr::{ + NonNull, + read + } + }; + + let this = ManuallyDrop::new(this); + let (length, on_heap) = this.length.parts(); + let source = unsafe { this.raw.as_ptr(on_heap) }; + let allocator = unsafe { read(&this.allocator) }; + + let mut boxed = Box::<[MaybeUninit], A>::new_uninit_slice_in(length, allocator); + unsafe { + copy_nonoverlapping(source, boxed.as_mut_ptr().cast(), length); + } + + if on_heap && size_of::() != 0 { + let (ptr, capacity) = unsafe { this.raw.heap }; + if capacity != 0 { + let layout = unsafe { + Layout::from_size_align_unchecked( + capacity * size_of::(), + core::mem::align_of::() + ) + }; + unsafe { + Allocator::deallocate( + &this.allocator, + NonNull::new_unchecked(ptr.as_ptr().cast()), + layout + ); + } + } + } + + let (ptr, allocator) = Box::into_raw_with_allocator(boxed); + let slice = core::ptr::slice_from_raw_parts_mut(ptr.cast(), length); + // SAFETY: every element was copied from an initialized `SmallVec`. + unsafe { Box::from_raw_in(slice, allocator) } +} diff --git a/src/lib.rs b/src/lib.rs index bb4a1d00..cd5d636b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -987,7 +987,9 @@ impl SmallVec { #[inline] #[deprecated(since = "2.0.0", note = "use `Into::>::into` instead")] pub fn into_boxed_slice(self) -> Box<[T]> { - self.into() + // `From` for `Box<[T], A>` is not always `Box<[T]>` (the `Global` + // box). Keep this deprecated helper on the global allocator. + Vec::from(self).into_boxed_slice() } #[inline] diff --git a/tests/main.rs b/tests/main.rs index 22ecfb9f..d42f0449 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1143,3 +1143,21 @@ fn spare_capacity_mut() { assert!(!spare.is_empty()); assert_eq!(spare.as_ptr().cast::(), unsafe { v.as_ptr().add(3) }); } + +#[test] +fn into_box_from_inline_and_spilled() { + let inline: SmallVec = SmallVec::from([1, 2, 3]); + assert!(!inline.spilled()); + let boxed: Box<[u32]> = inline.into(); + assert_eq!(&*boxed, &[1, 2, 3]); + + let mut spilled: SmallVec = SmallVec::new(); + spilled.extend(1..6); + assert!(spilled.spilled()); + let boxed: Box<[u32]> = spilled.into(); + assert_eq!(&*boxed, &[1, 2, 3, 4, 5]); + + let empty: SmallVec = SmallVec::new(); + let boxed: Box<[u32]> = empty.into(); + assert!(boxed.is_empty()); +}