Created
August 24, 2025 08:12
-
-
Save Techcable/a06f74db0f62cf31521cf917ddaae78d to your computer and use it in GitHub Desktop.
[DRAFT] An implementation of the stdlib-internal `RawVec` type using the `Vec` for the allocation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::marker::PhantomData; | |
| use std::mem::ManuallyDrop; | |
| use std::ptr::NonNull; | |
| /// Check if growth is necessary. | |
| #[inline] | |
| pub fn needs_to_grow(capacity: usize, len: usize, additional: usize) -> bool { | |
| // https://github.com/rust-lang/rust/blob/1.89.0/library/alloc/src/raw_vec/mod.rs#L626-L628 | |
| additional > capacity.wrapping_sub(len) | |
| } | |
| /// Similar to the stdlib-internal [`RawVec`] type, | |
| /// but using a [`Vec`] for the underlying allocation logic | |
| /// instead of reimplementing from scratch. | |
| /// | |
| /// This is perfectly safe due to the guarantees made by the [`Vec`] type. | |
| /// | |
| /// [`RawVec`]: https://github.com/rust-lang/rust/blob/1.89.0/library/alloc/src/raw_vec/mod.rs#L51-L74 | |
| pub struct RawVec<T> { | |
| /// Pointer to the allocated memory, or dangling if `capacity == 0`. | |
| /// | |
| /// The use of [`ManuallyDrop`] means the destructor of `T` can be statically known to be unnecessary, | |
| /// hopefully avoiding some overhead when converted back into a `Vec` during drop. | |
| ptr: NonNull<ManuallyDrop<T>>, | |
| capacity: usize, | |
| marker: PhantomData<T>, | |
| } | |
| impl<T> Default for RawVec<T> { | |
| #[inline] | |
| fn default() -> Self { | |
| Self::new() | |
| } | |
| } | |
| impl<T> RawVec<T> { | |
| /// If conversion to/from a `Vec` needs an extra check for `capacity == 0`, | |
| /// to avoid calling `Vec::from_raw_parts` on a dangling pointer. | |
| /// | |
| /// Technically, this is undefined behavior, so we avoid it. | |
| /// On newer versions we can initialize to `const { Vec::new().as_ptr() }` instead of a | |
| /// dangling pointer, avoiding the check entirely. | |
| const NEED_DANGLING_CHECK: bool = rustversion::cfg!(before(1.87)); | |
| #[inline] | |
| #[rustversion::since(1.87)] | |
| pub const fn new() -> Self { | |
| const { assert!(!Self::NEED_DANGLING_CHECK) } | |
| Self::from_vec(Vec::new()) | |
| } | |
| #[inline] | |
| #[rustversion::before(1.87)] | |
| pub const fn new() -> Self { | |
| const { | |
| assert!(Self::NEED_DANGLING_CHECK); | |
| } | |
| RawVec { | |
| ptr: NonNull::dangling(), | |
| capacity: 0, | |
| marker: PhantomData, | |
| } | |
| } | |
| /// Grow the vector to meet the specified capacity if it is necessary to do so. | |
| #[inline] | |
| pub fn grow(&mut self, required_capacity: usize) { | |
| if required_capacity > self.capacity { | |
| self.grow_slow(required_capacity) | |
| } | |
| } | |
| /// Cold-path function to actually grow the vector. | |
| #[cold] | |
| pub fn grow_slow(&mut self, required_capacity: usize) { | |
| // SAFETY: Not dropped | |
| let mut vec = unsafe { self.as_vec_unchecked() }; | |
| debug_assert_eq!(vec.len(), 0); | |
| vec.reserve(required_capacity); | |
| debug_assert!(vec.capacity() >= required_capacity); | |
| debug_assert!(vec.capacity() >= self.capacity); | |
| self.capacity = vec.capacity(); | |
| } | |
| /// Convert a `Vec<ManuallyDrop<T>>` into a `RawVec`. | |
| /// | |
| /// The elements themselves will be leaked, | |
| /// but this is fine since | |
| #[rustversion::attr(since(1.87), const)] // const Vec::as_ptr | |
| #[rustversion::attr(before(1.87), allow(dead_code))] | |
| #[inline] | |
| const fn from_vec(mut vec: Vec<ManuallyDrop<T>>) -> Self { | |
| debug_assert!(vec.len() == 0); | |
| // SAFETY: A vector's pointer is never null | |
| let ptr = unsafe { NonNull::new_unchecked(vec.as_mut_ptr()) }; | |
| let capacity = vec.capacity(); | |
| std::mem::forget(vec); | |
| RawVec { | |
| ptr, | |
| capacity, | |
| marker: PhantomData, | |
| } | |
| } | |
| /// Convert this value into a vector, without consuming ownership. | |
| /// | |
| /// # Safety | |
| /// Will lead to double-free if both theis `RawVec` and the returned vec are dropped. | |
| #[inline] | |
| unsafe fn as_vec_unchecked(&self) -> ManuallyDrop<Vec<ManuallyDrop<T>>> { | |
| // SAFETY: Parts are valid | |
| unsafe { | |
| if Self::NEED_DANGLING_CHECK && self.capacity == 0 { | |
| ManuallyDrop::new(Vec::new()) | |
| } else { | |
| ManuallyDrop::new(Vec::from_raw_parts( | |
| self.ptr.as_ptr(), | |
| 0, // no length | |
| self.capacity, | |
| )) | |
| } | |
| } | |
| } | |
| } | |
| #[cfg(feature = "nightly")] | |
| impl<#[may_dangle] T> Drop for RawVec<T> { | |
| fn drop(&mut self) { | |
| // SAFETY: No double drop due to being called in drop | |
| drop(unsafe { self.as_vec_unchecked() }); | |
| } | |
| } | |
| #[cfg(not(feature = "nightly"))] | |
| impl<T> Drop for RawVec<T> { | |
| fn drop(&mut self) { | |
| // SAFETY: No double drop due to being called in drop | |
| drop(unsafe { self.as_vec_unchecked() }); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment