From 19df24b3b8778d929bbef50bf64330defa1412bb Mon Sep 17 00:00:00 2001 From: SabrinaJewson Date: Mon, 11 Aug 2025 17:09:01 +0100 Subject: [PATCH 1/3] =?UTF-8?q?Make=20explicit=20guarantees=20about=20`Vec?= =?UTF-8?q?`=E2=80=99s=20allocator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- library/alloc/src/vec/mod.rs | 51 +++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 2321aab2c516e..5258aaffd8307 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -49,7 +49,26 @@ //! v[1] = v[1] + 5; //! ``` //! +//! # Memory layout +//! +//! For non-zero-sized values, a [`Vec`] will use the [`Global`] allocator for its allocation. It is +//! valid to convert both ways between a [`Vec`] and a raw pointer allocated with the [`Global`] +//! allocator, given that the [`Layout`] used with the allocator is correct for a sequence of +//! `capacity` values 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::(capacity)`][Layout::array] may be converted into a vec using +//! [`Vec::::from_raw_parts(ptr, len, capacity)`](Vec::from_raw_parts). +//! Conversely, the memory backing a `value: *mut T` obtained from [`Vec::::as_mut_ptr`] may be +//! deallocated using the [`Global`] allocator with the same layout. +//! +//! For zero-sized values, the `Vec` pointer has to 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")] @@ -770,12 +789,15 @@ impl Vec { /// 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 one may use any method that calls + /// [`dealloc`] with a layout of `Layout::array::(capacity)`, + /// and if `T` is zero-sized nothing needs to be done. /// /// [`from_raw_parts`]: Vec::from_raw_parts + /// [`dealloc`]: crate::alloc::GlobalAlloc::dealloc /// /// # Examples /// @@ -1755,6 +1777,12 @@ impl Vec { /// 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, the pointer may be + /// passed into [`dealloc`] with a layout of `Layout::array::(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 +1815,24 @@ impl Vec { /// } /// ``` /// + /// 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] = + /// 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] From 45e2449b2af5cb8e96a60cddafe4b6a49568a937 Mon Sep 17 00:00:00 2001 From: SabrinaJewson Date: Mon, 11 Aug 2025 19:02:00 +0100 Subject: [PATCH 2/3] Respond to review comments --- library/alloc/src/vec/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 5258aaffd8307..e02265dcafca3 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -51,18 +51,18 @@ //! //! # Memory layout //! -//! For non-zero-sized values, a [`Vec`] will use the [`Global`] allocator for its allocation. It is +//! For non-zero-sized types, [`Vec`] uses the [`Global`] allocator for its allocation. It is //! valid to convert both ways between a [`Vec`] and a raw pointer allocated with the [`Global`] -//! allocator, given that the [`Layout`] used with the allocator is correct for a sequence of -//! `capacity` values of the type, and the first `len` values pointed to by the raw pointer are +//! 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::(capacity)`][Layout::array] may be converted into a vec using //! [`Vec::::from_raw_parts(ptr, len, capacity)`](Vec::from_raw_parts). //! Conversely, the memory backing a `value: *mut T` obtained from [`Vec::::as_mut_ptr`] may be //! deallocated using the [`Global`] allocator with the same layout. //! -//! For zero-sized values, the `Vec` pointer has to be non-null and sufficiently aligned. The -//! recommended way to build a `Vec` of ZSTs if [`vec!`] cannot be used is to use +//! For zero-sized types (ZSTs), 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 From 1ce4b370e44fbac89b75438c8e75b26bff18d496 Mon Sep 17 00:00:00 2001 From: SabrinaJewson Date: Tue, 12 Aug 2025 05:12:34 +0100 Subject: [PATCH 3/3] Handle the `capacity == 0` case --- library/alloc/src/vec/mod.rs | 61 ++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index e02265dcafca3..8001faf9d20a5 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -51,19 +51,20 @@ //! //! # Memory layout //! -//! For non-zero-sized types, [`Vec`] uses the [`Global`] allocator for its allocation. It is -//! valid to convert both ways between 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::(capacity)`][Layout::array] may be converted into a vec using -//! [`Vec::::from_raw_parts(ptr, len, capacity)`](Vec::from_raw_parts). -//! Conversely, the memory backing a `value: *mut T` obtained from [`Vec::::as_mut_ptr`] may be -//! deallocated using the [`Global`] allocator with the same 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::(capacity)`][Layout::array] may +//! be converted into a vec using +//! [`Vec::::from_raw_parts(ptr, len, capacity)`](Vec::from_raw_parts). Conversely, the memory +//! backing a `value: *mut T` obtained from [`Vec::::as_mut_ptr`] may be deallocated using the +//! [`Global`] allocator with the same layout. //! -//! For zero-sized types (ZSTs), 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`]. +//! 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 @@ -542,18 +543,23 @@ impl Vec { /// 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`]. /// @@ -792,9 +798,10 @@ impl Vec { /// 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 one may use any method that calls - /// [`dealloc`] with a layout of `Layout::array::(capacity)`, - /// and if `T` is zero-sized nothing needs to be done. + /// 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::(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 @@ -1777,11 +1784,11 @@ impl Vec { /// 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, the pointer may be - /// passed into [`dealloc`] with a layout of `Layout::array::(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. + /// 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::(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 ///