diff options
| author | Stuart Cook <Zalathar@users.noreply.github.com> | 2025-08-12 20:37:56 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-12 20:37:56 +1000 |
| commit | 30ed54e3e4de885cc40597d2b9d7f326c9c61151 (patch) | |
| tree | eb3c524ec28f1985402f9cef7c3f189cd96f8d10 | |
| parent | 1c38519319a72ab197c0820d2e61b3bfea7d08fc (diff) | |
| parent | 1ce4b370e44fbac89b75438c8e75b26bff18d496 (diff) | |
| download | rust-30ed54e3e4de885cc40597d2b9d7f326c9c61151.tar.gz rust-30ed54e3e4de885cc40597d2b9d7f326c9c61151.zip | |
Rollup merge of #145260 - SabrinaJewson:vec-allocator-docs, r=dtolnay
Make explicit guarantees about `Vec`’s allocator This commit amends the documentation of `Vec::as_mut_ptr` and `Vec::into_raw_parts` to make it explicit that such calls may be paired with calls to `dealloc` with a suitable layout. This guarantee was effectively already provided by the docs of `Vec::from_raw_parts` mentioning `alloc`. Additionally, we copy-paste and adjust the “Memory layout” section from the documentation of `std::boxed` to `std::vec`. This explains the allocator guarantees in more detail.
| -rw-r--r-- | library/alloc/src/vec/mod.rs | 72 |
1 files changed, 61 insertions, 11 deletions
diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 2321aab2c51..8001faf9d20 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -49,7 +49,27 @@ //! v[1] = v[1] + 5; //! ``` //! +//! # Memory layout +//! +//! When the type is non-zero-sized and the capacity is nonzero, [`Vec`] uses the [`Global`] +//! allocator for its allocation. It is valid to convert both ways between such a [`Vec`] and a raw +//! pointer allocated with the [`Global`] allocator, provided that the [`Layout`] used with the +//! allocator is correct for a sequence of `capacity` elements of the type, and the first `len` +//! values pointed to by the raw pointer are valid. More precisely, a `ptr: *mut T` that has been +//! allocated with the [`Global`] allocator with [`Layout::array::<T>(capacity)`][Layout::array] may +//! be converted into a vec using +//! [`Vec::<T>::from_raw_parts(ptr, len, capacity)`](Vec::from_raw_parts). Conversely, the memory +//! backing a `value: *mut T` obtained from [`Vec::<T>::as_mut_ptr`] may be deallocated using the +//! [`Global`] allocator with the same layout. +//! +//! For zero-sized types (ZSTs), or when the capacity is zero, the `Vec` pointer must be non-null +//! and sufficiently aligned. The recommended way to build a `Vec` of ZSTs if [`vec!`] cannot be +//! used is to use [`ptr::NonNull::dangling`]. +//! //! [`push`]: Vec::push +//! [`ptr::NonNull::dangling`]: NonNull::dangling +//! [`Layout`]: crate::alloc::Layout +//! [Layout::array]: crate::alloc::Layout::array #![stable(feature = "rust1", since = "1.0.0")] @@ -523,18 +543,23 @@ impl<T> Vec<T> { /// 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. + /// * If `T` is not a zero-sized type and the capacity is nonzero, `ptr` must have + /// been allocated using the global allocator, such as via the [`alloc::alloc`] + /// function. If `T` is a zero-sized type or the capacity is zero, `ptr` need + /// only be non-null and aligned. + /// * `T` needs to have the same alignment as what `ptr` was allocated with, + /// if the pointer is required to be allocated. /// (`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`.) + /// * The size of `T` times the `capacity` (ie. the allocated size in bytes), if + /// nonzero, 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. + /// * `capacity` needs to be the capacity that the pointer was allocated with, + /// if the pointer is required to be allocated. /// * The allocated size in bytes must be no larger than `isize::MAX`. /// See the safety documentation of [`pointer::offset`]. /// @@ -770,12 +795,16 @@ impl<T> Vec<T> { /// order as the arguments to [`from_raw_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 raw pointer, length, and capacity back - /// into a `Vec` with the [`from_raw_parts`] function, allowing - /// the destructor to perform the cleanup. + /// memory previously managed by the `Vec`. Most often, one does + /// this by converting the raw pointer, length, and capacity back + /// into a `Vec` with the [`from_raw_parts`] function; more generally, + /// if `T` is non-zero-sized and the capacity is nonzero, one may use + /// any method that calls [`dealloc`] with a layout of + /// `Layout::array::<T>(capacity)`; if `T` is zero-sized or the + /// capacity is zero, nothing needs to be done. /// /// [`from_raw_parts`]: Vec::from_raw_parts + /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc /// /// # Examples /// @@ -1755,6 +1784,12 @@ impl<T, A: Allocator> Vec<T, A> { /// may still invalidate this pointer. /// See the second example below for how this guarantee can be used. /// + /// The method also guarantees that, as long as `T` is not zero-sized and the capacity is + /// nonzero, the pointer may be passed into [`dealloc`] with a layout of + /// `Layout::array::<T>(capacity)` in order to deallocate the backing memory. If this is done, + /// be careful not to run the destructor of the `Vec`, as dropping it will result in + /// double-frees. Wrapping the `Vec` in a [`ManuallyDrop`] is the typical way to achieve this. + /// /// # Examples /// /// ``` @@ -1787,9 +1822,24 @@ impl<T, A: Allocator> Vec<T, A> { /// } /// ``` /// + /// Deallocating a vector using [`Box`] (which uses [`dealloc`] internally): + /// + /// ``` + /// use std::mem::{ManuallyDrop, MaybeUninit}; + /// + /// let mut v = ManuallyDrop::new(vec![0, 1, 2]); + /// let ptr = v.as_mut_ptr(); + /// let capacity = v.capacity(); + /// let slice_ptr: *mut [MaybeUninit<i32>] = + /// std::ptr::slice_from_raw_parts_mut(ptr.cast(), capacity); + /// drop(unsafe { Box::from_raw(slice_ptr) }); + /// ``` + /// /// [`as_mut_ptr`]: Vec::as_mut_ptr /// [`as_ptr`]: Vec::as_ptr /// [`as_non_null`]: Vec::as_non_null + /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc + /// [`ManuallyDrop`]: core::mem::ManuallyDrop #[stable(feature = "vec_as_ptr", since = "1.37.0")] #[rustc_const_stable(feature = "const_vec_string_slice", since = "1.87.0")] #[rustc_never_returns_null_ptr] |
