Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Add NonNull convenience methods to Vec
  • Loading branch information
theemathas committed Sep 7, 2024
commit 8230a90c494dcc7d8013a0a15719cc9f2f9a8263
2 changes: 1 addition & 1 deletion library/alloc/src/vec/in_place_collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ where

mem::forget(dst_guard);

let vec = unsafe { Vec::from_nonnull(dst_buf, len, dst_cap) };
let vec = unsafe { Vec::from_parts(dst_buf, len, dst_cap) };

vec
}
Expand Down
320 changes: 306 additions & 14 deletions library/alloc/src/vec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -603,15 +603,116 @@ impl<T> Vec<T> {
unsafe { Self::from_raw_parts_in(ptr, length, capacity, Global) }
}

/// A convenience method for hoisting the non-null precondition out of [`Vec::from_raw_parts`].
#[doc(alias = "from_non_null_parts")]
/// Creates a `Vec<T>` directly from a `NonNull` pointer, a length, and a capacity.
///
/// # Safety
///
/// See [`Vec::from_raw_parts`].
/// This is highly unsafe, due to the number of invariants that aren't
/// checked:
///
/// * `ptr` must have been allocated using the global allocator, such as via
/// the [`alloc::alloc`] function.
/// * `T` needs to have the same alignment as what `ptr` was allocated with.
/// (`T` having a less strict alignment is not sufficient, the alignment really
/// needs to be equal to satisfy the [`dealloc`] requirement that memory must be
/// allocated and deallocated with the same layout.)
/// * The size of `T` times the `capacity` (ie. the allocated size in bytes) needs
/// to be the same size as the pointer was allocated with. (Because similar to
/// alignment, [`dealloc`] must be called with the same layout `size`.)
/// * `length` needs to be less than or equal to `capacity`.
/// * The first `length` values must be properly initialized values of type `T`.
/// * `capacity` needs to be the capacity that the pointer was allocated with.
/// * The allocated size in bytes must be no larger than `isize::MAX`.
/// See the safety documentation of [`pointer::offset`].
///
/// These requirements are always upheld by any `ptr` that has been allocated
/// via `Vec<T>`. Other allocation sources are allowed if the invariants are
/// upheld.
///
/// Violating these may cause problems like corrupting the allocator's
/// internal data structures. For example it is normally **not** safe
/// to build a `Vec<u8>` from a pointer to a C `char` array with length
/// `size_t`, doing so is only safe if the array was initially allocated by
/// a `Vec` or `String`.
/// It's also not safe to build one from a `Vec<u16>` and its length, because
/// the allocator cares about the alignment, and these two types have different
/// alignments. The buffer was allocated with alignment 2 (for `u16`), but after
/// turning it into a `Vec<u8>` it'll be deallocated with alignment 1. To avoid
/// these issues, it is often preferable to do casting/transmuting using
/// [`NonNull::slice_from_raw_parts`] instead.
///
/// The ownership of `ptr` is effectively transferred to the
/// `Vec<T>` which may then deallocate, reallocate or change the
/// contents of memory pointed to by the pointer at will. Ensure
/// that nothing else uses the pointer after calling this
/// function.
///
/// [`String`]: crate::string::String
/// [`alloc::alloc`]: crate::alloc::alloc
/// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc
///
/// # Examples
///
/// ```
/// #![feature(box_vec_non_null)]
///
/// use std::ptr::NonNull;
/// use std::mem;
///
/// let v = vec![1, 2, 3];
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// // Prevent running `v`'s destructor so we are in complete control
/// // of the allocation.
/// let mut v = mem::ManuallyDrop::new(v);
///
/// // Pull out the various important pieces of information about `v`
/// let p = unsafe { NonNull::new_unchecked(v.as_mut_ptr()) };
/// let len = v.len();
/// let cap = v.capacity();
///
/// unsafe {
/// // Overwrite memory with 4, 5, 6
/// for i in 0..len {
/// p.add(i).write(4 + i);
/// }
///
/// // Put everything back together into a Vec
/// let rebuilt = Vec::from_parts(p, len, cap);
/// assert_eq!(rebuilt, [4, 5, 6]);
/// }
/// ```
///
/// Using memory that was allocated elsewhere:
///
/// ```rust
/// #![feature(box_vec_non_null)]
///
/// use std::alloc::{alloc, Layout};
/// use std::ptr::NonNull;
///
/// fn main() {
/// let layout = Layout::array::<u32>(16).expect("overflow cannot happen");
///
/// let vec = unsafe {
/// let Some(mem) = NonNull::new(alloc(layout).cast::<u32>()) else {
/// return;
/// };
///
/// mem.write(1_000_000);
///
/// Vec::from_parts(mem, 1, 16)
/// };
///
/// assert_eq!(vec, &[1_000_000]);
/// assert_eq!(vec.capacity(), 16);
/// }
/// ```
#[inline]
#[cfg(not(no_global_oom_handling))] // required by tests/run-make/alloc-no-oom-handling
pub(crate) unsafe fn from_nonnull(ptr: NonNull<T>, length: usize, capacity: usize) -> Self {
unsafe { Self::from_nonnull_in(ptr, length, capacity, Global) }
#[unstable(feature = "box_vec_non_null", reason = "new API", issue = "none")]
pub unsafe fn from_parts(ptr: NonNull<T>, length: usize, capacity: usize) -> Self {
unsafe { Self::from_parts_in(ptr, length, capacity, Global) }
}
}

Expand Down Expand Up @@ -830,19 +931,119 @@ impl<T, A: Allocator> Vec<T, A> {
unsafe { Vec { buf: RawVec::from_raw_parts_in(ptr, capacity, alloc), len: length } }
}

/// A convenience method for hoisting the non-null precondition out of [`Vec::from_raw_parts_in`].
#[doc(alias = "from_non_null_parts_in")]
/// Creates a `Vec<T, A>` directly from a `NonNull` pointer, a length, a capacity,
/// and an allocator.
///
/// # Safety
///
/// See [`Vec::from_raw_parts_in`].
/// This is highly unsafe, due to the number of invariants that aren't
/// checked:
///
/// * `ptr` must be [*currently allocated*] via the given allocator `alloc`.
/// * `T` needs to have the same alignment as what `ptr` was allocated with.
/// (`T` having a less strict alignment is not sufficient, the alignment really
/// needs to be equal to satisfy the [`dealloc`] requirement that memory must be
/// allocated and deallocated with the same layout.)
/// * The size of `T` times the `capacity` (ie. the allocated size in bytes) needs
/// to be the same size as the pointer was allocated with. (Because similar to
/// alignment, [`dealloc`] must be called with the same layout `size`.)
/// * `length` needs to be less than or equal to `capacity`.
/// * The first `length` values must be properly initialized values of type `T`.
/// * `capacity` needs to [*fit*] the layout size that the pointer was allocated with.
/// * The allocated size in bytes must be no larger than `isize::MAX`.
/// See the safety documentation of [`pointer::offset`].
///
/// These requirements are always upheld by any `ptr` that has been allocated
/// via `Vec<T, A>`. Other allocation sources are allowed if the invariants are
/// upheld.
///
/// Violating these may cause problems like corrupting the allocator's
/// internal data structures. For example it is **not** safe
/// to build a `Vec<u8>` from a pointer to a C `char` array with length `size_t`.
/// It's also not safe to build one from a `Vec<u16>` and its length, because
/// the allocator cares about the alignment, and these two types have different
/// alignments. The buffer was allocated with alignment 2 (for `u16`), but after
/// turning it into a `Vec<u8>` it'll be deallocated with alignment 1.
///
/// The ownership of `ptr` is effectively transferred to the
/// `Vec<T>` which may then deallocate, reallocate or change the
/// contents of memory pointed to by the pointer at will. Ensure
/// that nothing else uses the pointer after calling this
/// function.
///
/// [`String`]: crate::string::String
/// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc
/// [*currently allocated*]: crate::alloc::Allocator#currently-allocated-memory
/// [*fit*]: crate::alloc::Allocator#memory-fitting
///
/// # Examples
///
/// ```
/// #![feature(allocator_api, box_vec_non_null)]
///
/// use std::alloc::System;
///
/// use std::ptr::NonNull;
/// use std::mem;
///
/// let mut v = Vec::with_capacity_in(3, System);
/// v.push(1);
/// v.push(2);
/// v.push(3);
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// // Prevent running `v`'s destructor so we are in complete control
/// // of the allocation.
/// let mut v = mem::ManuallyDrop::new(v);
///
/// // Pull out the various important pieces of information about `v`
/// let p = unsafe { NonNull::new_unchecked(v.as_mut_ptr()) };
/// let len = v.len();
/// let cap = v.capacity();
/// let alloc = v.allocator();
///
/// unsafe {
/// // Overwrite memory with 4, 5, 6
/// for i in 0..len {
/// p.add(i).write(4 + i);
/// }
///
/// // Put everything back together into a Vec
/// let rebuilt = Vec::from_parts_in(p, len, cap, alloc.clone());
/// assert_eq!(rebuilt, [4, 5, 6]);
/// }
/// ```
///
/// Using memory that was allocated elsewhere:
///
/// ```rust
/// #![feature(allocator_api, box_vec_non_null)]
///
/// use std::alloc::{AllocError, Allocator, Global, Layout};
///
/// fn main() {
/// let layout = Layout::array::<u32>(16).expect("overflow cannot happen");
///
/// let vec = unsafe {
/// let mem = match Global.allocate(layout) {
/// Ok(mem) => mem.cast::<u32>(),
/// Err(AllocError) => return,
/// };
///
/// mem.write(1_000_000);
///
/// Vec::from_parts_in(mem, 1, 16, Global)
/// };
///
/// assert_eq!(vec, &[1_000_000]);
/// assert_eq!(vec.capacity(), 16);
/// }
/// ```
#[inline]
#[cfg(not(no_global_oom_handling))] // required by tests/run-make/alloc-no-oom-handling
pub(crate) unsafe fn from_nonnull_in(
ptr: NonNull<T>,
length: usize,
capacity: usize,
alloc: A,
) -> Self {
#[unstable(feature = "allocator_api", reason = "new API", issue = "32838")]
// #[unstable(feature = "box_vec_non_null", issue = "none")]
pub unsafe fn from_parts_in(ptr: NonNull<T>, length: usize, capacity: usize, alloc: A) -> Self {
unsafe { Vec { buf: RawVec::from_nonnull_in(ptr, capacity, alloc), len: length } }
}

Expand Down Expand Up @@ -885,6 +1086,49 @@ impl<T, A: Allocator> Vec<T, A> {
(me.as_mut_ptr(), me.len(), me.capacity())
}

#[doc(alias = "into_non_null_parts")]
/// Decomposes a `Vec<T>` into its raw components: `(NonNull pointer, length, capacity)`.
///
/// Returns the `NonNull` pointer to the underlying data, the length of
/// the vector (in elements), and the allocated capacity of the
/// data (in elements). These are the same arguments in the same
/// order as the arguments to [`from_parts`].
///
/// After calling this function, the caller is responsible for the
/// memory previously managed by the `Vec`. The only way to do
/// this is to convert the `NonNull` pointer, length, and capacity back
/// into a `Vec` with the [`from_parts`] function, allowing
/// the destructor to perform the cleanup.
///
/// [`from_parts`]: Vec::from_parts
///
/// # Examples
///
/// ```
/// #![feature(vec_into_raw_parts, box_vec_non_null)]
///
/// let v: Vec<i32> = vec![-1, 0, 1];
///
/// let (ptr, len, cap) = v.into_parts();
///
/// let rebuilt = unsafe {
/// // We can now make changes to the components, such as
/// // transmuting the raw pointer to a compatible type.
/// let ptr = ptr.cast::<u32>();
///
/// Vec::from_parts(ptr, len, cap)
/// };
/// assert_eq!(rebuilt, [4294967295, 0, 1]);
/// ```
#[must_use = "losing the pointer will leak memory"]
#[unstable(feature = "box_vec_non_null", reason = "new API", issue = "none")]
// #[unstable(feature = "vec_into_raw_parts", reason = "new API", issue = "65816")]
pub fn into_parts(self) -> (NonNull<T>, usize, usize) {
let (ptr, len, capacity) = self.into_raw_parts();
// SAFETY: A `Vec` always has a non-null pointer.
(unsafe { NonNull::new_unchecked(ptr) }, len, capacity)
}

/// Decomposes a `Vec<T>` into its raw components: `(pointer, length, capacity, allocator)`.
///
/// Returns the raw pointer to the underlying data, the length of the vector (in elements),
Expand Down Expand Up @@ -934,6 +1178,54 @@ impl<T, A: Allocator> Vec<T, A> {
(ptr, len, capacity, alloc)
}

#[doc(alias = "into_non_null_parts_with_alloc")]
/// Decomposes a `Vec<T>` into its raw components: `(NonNull pointer, length, capacity, allocator)`.
///
/// Returns the `NonNull` pointer to the underlying data, the length of the vector (in elements),
/// the allocated capacity of the data (in elements), and the allocator. These are the same
/// arguments in the same order as the arguments to [`from_parts_in`].
///
/// After calling this function, the caller is responsible for the
/// memory previously managed by the `Vec`. The only way to do
/// this is to convert the `NonNull` pointer, length, and capacity back
/// into a `Vec` with the [`from_parts_in`] function, allowing
/// the destructor to perform the cleanup.
///
/// [`from_parts_in`]: Vec::from_parts_in
///
/// # Examples
///
/// ```
/// #![feature(allocator_api, vec_into_raw_parts, box_vec_non_null)]
///
/// use std::alloc::System;
///
/// let mut v: Vec<i32, System> = Vec::new_in(System);
/// v.push(-1);
/// v.push(0);
/// v.push(1);
///
/// let (ptr, len, cap, alloc) = v.into_parts_with_alloc();
///
/// let rebuilt = unsafe {
/// // We can now make changes to the components, such as
/// // transmuting the raw pointer to a compatible type.
/// let ptr = ptr.cast::<u32>();
///
/// Vec::from_parts_in(ptr, len, cap, alloc)
/// };
/// assert_eq!(rebuilt, [4294967295, 0, 1]);
/// ```
#[must_use = "losing the pointer will leak memory"]
#[unstable(feature = "allocator_api", issue = "32838")]
// #[unstable(feature = "box_vec_non_null", reason = "new API", issue = "none")]
// #[unstable(feature = "vec_into_raw_parts", reason = "new API", issue = "65816")]
pub fn into_parts_with_alloc(self) -> (NonNull<T>, usize, usize, A) {
let (ptr, len, capacity, alloc) = self.into_raw_parts_with_alloc();
// SAFETY: A `Vec` always has a non-null pointer.
(unsafe { NonNull::new_unchecked(ptr) }, len, capacity, alloc)
}

/// Returns the total number of elements the vector can hold without
/// reallocating.
///
Expand Down
2 changes: 1 addition & 1 deletion library/alloc/src/vec/spec_from_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl<T> SpecFromIter<T, IntoIter<T>> for Vec<T> {
if has_advanced {
ptr::copy(it.ptr.as_ptr(), it.buf.as_ptr(), it.len());
}
return Vec::from_nonnull(it.buf, it.len(), it.cap);
return Vec::from_parts(it.buf, it.len(), it.cap);
}
}

Expand Down