From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 33B821FF0AB for ; Mon, 07 Sep 2026 13:44:25 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 7E89C214DC; Mon, 07 Sep 2026 13:44:24 +0200 (CEST) Mime-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=UTF-8 Date: Mon, 07 Sep 2026 13:44:11 +0200 Message-Id: To: "Robert Obkircher" From: "Max R. Carrara" Subject: Re: [PATCH proxmox v2 01/10] proxmox-alloc: introduce proxmox-alloc with `LayoutAwareBox` type X-Mailer: aerc 0.18.2-0-ge037c095a049 References: <20260821140238.615302-1-m.carrara@proxmox.com> <20260821140238.615302-2-m.carrara@proxmox.com> <178836567690.287871.2706501273479858915.b4-review@b4> In-Reply-To: <178836567690.287871.2706501273479858915.b4-review@b4> X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1788781444899 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.575 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) PROLO_LEO1 0.1 Meta Catches all Leo drug variations so far RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: 6DTEEPEREACUFG6JJXISK47LJE63KURO X-Message-ID-Hash: 6DTEEPEREACUFG6JJXISK47LJE63KURO X-MailFrom: m.carrara@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: pbs-devel@lists.proxmox.com X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: On Wed Sep 2, 2026 at 6:14 PM CEST, Robert Obkircher wrote: > > Introducte a new crate called `proxmox-alloc`, an allocation and > > collections library similar to `core::alloc`. > > > > Add `LayoutAwareBox`, a type which tracks the `Layout` its memory > > was allocated with. > > > > Rust requires that the alignment [align] an allocation uses matches > > that its corresponding deallocation uses. It is considered undefined > > behavior if the alignments do not match [ub]. Additionally, Rust does > > not (and cannot) track whether a given type's memory was allocated > > with a custom layout. Instead, it will always assume that the > > alignment of a given T's memory is align_of::(). > > > > `LayoutAwareBox` solves this issue by tracking which layout was > > used during T's allocation, passing it to `dealloc()` in its `drop` > > handler. > > > > While `LayoutAwareBox` does not provide any convenience methods for > > creating custom dynamically sized types (DSTs) [dst], it does provide > > a `from_raw_parts()` method to store the freshly allocated DST and its > > layout. > > > > Additionally, `LayoutAwareBox` provides several convenience methods > > for creating heap-allocated values and slices with a particular > > alignment. It also implements many of the standard library's traits > > wherever applicable. In particular, it implements almost all traits of > > `Box`, except: > > > > - unstable traits or trait impls that require unstable features > > (as of writing, Rust 1.94.1) > > > > - traits that involve strings or string-like types, such as > > `From for LayoutAwareBox`, > > `Clone for LayoutAwareBox`, > > `From for LayoutAwareBox`, and so on > > > > - traits that require non-trivial memory manipulation, such as > > `Clone for LayoutAwareBox where T: ?Sized` > > > > --> cloning the memory inside a `LayoutAwareBox` itself would be > > trivial, but if `T: ?Sized`, it cannot be `Clone`. > > Note that `Box` does not provide such an impl either. > > > > If we want to clone DSTs in the future, we will most likely need > > some kind of custom trait that we can rely on. > > > > - fn-traits, such as `Fn`, `AsyncFnMut`, etc. > > > > - `AsFd`, `AsHandle`, `Future`, and `Unpin` > > > > In other words, `LayoutAwareBox` will work fine for DSTs and > > buffers. Other trait impls can always be added later once we actually > > need them. > > > > Finally, this commit also adds ample documentation and tests for > > `LayoutAwareBox` in order to make it easier to use for developers > > who are not as familiar with lower-level Rust. > > > > [align]: https://en.wikipedia.org/wiki/Data_structure_alignment > > [dst]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#dynamically-= sized-types-dsts > > [ub]: https://doc.rust-lang.org/reference/behavior-considered-undefined= .html > > > > Signed-off-by: Max R. Carrara > > > > diff --git a/Cargo.toml b/Cargo.toml > > index 16e91c94..c00515e3 100644 > > --- a/Cargo.toml > > +++ b/Cargo.toml > > @@ -3,6 +3,7 @@ members =3D [ > > "proxmox-access-control", > > "proxmox-acme", > > "proxmox-acme-api", > > + "proxmox-alloc", > > "proxmox-api-macro", > > "proxmox-apt", > > "proxmox-apt-api-types", > > diff --git a/proxmox-alloc/Cargo.toml b/proxmox-alloc/Cargo.toml > > new file mode 100644 > > index 00000000..4eeeb787 > > --- /dev/null > > +++ b/proxmox-alloc/Cargo.toml > > @@ -0,0 +1,20 @@ > > +[package] > > +name =3D "proxmox-alloc" > > +version =3D "0.1.0" > > +description =3D "Proxmox allocation and collections library" > > + > > +authors.workspace =3D true > > +edition.workspace =3D true > > +rust-version.workspace =3D true > > +homepage.workspace =3D true > > +repository.workspace =3D true > > +license.workspace =3D true > > +exclude.workspace =3D true > > + > > +[dependencies] > > + > > +[dev-dependencies] > > +libc.workspace =3D true > > + > > +[features] > > +default =3D [] > > diff --git a/proxmox-alloc/src/aware_boxed.rs b/proxmox-alloc/src/aware= _boxed.rs > > new file mode 100644 > > index 00000000..4ec3811e > > --- /dev/null > > +++ b/proxmox-alloc/src/aware_boxed.rs > > @@ -0,0 +1,2196 @@ > > +//! The `LayoutAwareBox` type for more involved heap allocations. > > +//! > > +//! `LayoutAwareBox` is a pointer to a type that uniquely owns a he= ap > > +//! allocation of type `T`, while also keeping track of the [`Layout`]= that was > > +//! used for the allocation. > > +//! > > +//! This type works similar to the standard library's [`Box`] and is c= ommonly > > +//! used as a wrapper around [dynamically sized types (DSTs)][DST] and= types > > +//! that are allocated with a particular [alignment]. > > The commit message and this doc comment should explicitly mention that > the only problem that this type solves is over-alignment to a runtime > value. > > Box works perfectly fine [1] for both normal types and DSTs, as long as > the alignment is known at compile time, and specifying it on the type > using repr(align(n)) also helps the compiler produce better code. > > [1] https://play.rust-lang.org/?version=3Dstable&mode=3Ddebug&edition=3D2= 024&gist=3D863c1e6baff27e20c26264080ffb3642 Hmm, yeah I agree, I'll be more specific in both cases then. I might just include a more explicit section on when it is necessary to use LayoutAwareBox over Box. Reading over it again, I don't think I was precise enough in my wording -- "types that are allocated with a particular alignment" is way too vague. Thanks for spotting this, will fix in v2! > > > +//! > > +//! The main problem this type addresses it that the Rust compiler doe= s not (and > > +//! cannot) track the alignment that was used to allocate a certain > > +//! [`Box`](Box). To illustrate, consider this example: > > +//! > > +//! ```no_run > > +//! // For illustration purposes only; do *not* use this in your code. > > +//! let buf_len =3D 1024; > > +//! let align =3D 128; > > +//! > > +//! let layout =3D std::alloc::Layout::from_size_align(buf_len, align)= .unwrap(); > > +//! > > +//! let thin_ptr: *mut u8 =3D unsafe { std::alloc::alloc_zeroed(layout= ) }; > > +//! > > +//! let fat_ptr: *mut [u8] =3D std::ptr::slice_from_raw_parts_mut(thin= _ptr, buf_len); > > +//! assert!(!fat_ptr.is_null()); > > +//! > > +//! let boxed: Box<[u8]> =3D unsafe { Box::from_raw(fat_ptr) }; > > +//! > > +//! drop(boxed); > > +//! ``` > > +//! > > +//! Here we allocate a simple buffer containing 1024 bytes, with an al= ignment of > > +//! 128. [`alloc_zeroed`] returns a plain `*mut u8`, which we then con= vert to a > > +//! pointer to a slice (or "fat pointer") that is aware of its own siz= e. > > +//! Finally, we pass this new pointer to [`Box::from_raw`] and create = a new > > +//! `Box<[u8]>` which now owns our heap-allocated buffer with our fanc= y > > +//! alignment. The [`Box`] will make sure that our buffer is deallocat= ed > > +//! correctly once dropped. We make this explicit here by using [`drop= `] > > +//! directly. > > +//! > > +//! While our logic might seem sound here, this will actually be repor= ted as > > +//! [undefined behavior] by [Miri]: > > +//! > > +//! ```text > > +//! error: Undefined Behavior: incorrect layout on deallocation: > > +//! alloc42389 has size 1024 and alignment 128, but gave size 1024 and= alignment 1! > > +//! ``` > > +//! > > +//! Therefore, one must always track the [`Layout`] that was used for = "exotic" > > +//! kinds of allocations, which [`LayoutAwareBox`] will do for you. > > +//! > > +//! # Examples > > +//! > > +//! Move a value from the stack on the heap, using a specific alignmen= t for the > > +//! allocation: > > +//! > > +//! ``` > > +//! use proxmox_alloc::LayoutAwareBox; > > +//! > > +//! struct SomeData { > > +//! first: u32, > > +//! second: u32, > > +//! } > > +//! > > +//! let value =3D SomeData { first: 10, second: 20 }; > > +//! > > +//! let aware_box: LayoutAwareBox =3D LayoutAwareBox::new(va= lue, 64) > > +//! .expect("infallible"); > > +//! ``` > > +//! > > +//! Create a page-size-aligned, zero-initialized buffer using > > +//! [`LayoutAwareBox::slice_fill`]: > > +//! > > +//! ``` > > +//! use libc; > > +//! > > +//! use proxmox_alloc::LayoutAwareBox; > > +//! > > +//! let buf_len =3D 2usize.pow(14); > > +//! > > +//! let page_size: i64 =3D unsafe { libc::sysconf(libc::_SC_PAGESIZE) = }; > > +//! assert!(page_size > 0, "failed to query PAGESIZE"); > > +//! > > +//! let align =3D page_size as usize; > > +//! > > +//! let pagesize_buf =3D LayoutAwareBox::<[u8]>::slice_fill(buf_len, a= lign, 0).unwrap(); > > +//! > > +//! assert_eq!(pagesize_buf.len(), buf_len); > > +//! ``` > > +//! > > +//! The same can also be done by using [`LayoutAwareBox::slice_fill_wi= th`] and > > +//! passing [`Default::default`]: > > +//! > > +//! ``` > > +//! # use libc; > > +//! # use proxmox_alloc::LayoutAwareBox; > > +//! # let buf_len =3D 2usize.pow(14); > > +//! # let page_size: i64 =3D unsafe { libc::sysconf(libc::_SC_PAGESIZE= ) }; > > +//! # assert!(page_size > 0, "failed to query PAGESIZE"); > > +//! # let align =3D page_size as usize; > > +//! let pagesize_buf =3D > > +//! LayoutAwareBox::<[u8]>::slice_fill_with(buf_len, align, Defaul= t::default).unwrap(); > > +//! ``` > > +//! > > +//! Note that if you want to retrieve the layout that was used for the > > +//! allocation, you should use [`LayoutAwareBox::layout`]: > > +//! > > +//! ``` > > +//! # use libc; > > +//! # use proxmox_alloc::LayoutAwareBox; > > +//! # let buf_len =3D 2usize.pow(14); > > +//! # let page_size: i64 =3D unsafe { libc::sysconf(libc::_SC_PAGESIZE= ) }; > > +//! # assert!(page_size > 0, "failed to query PAGESIZE"); > > +//! let align =3D page_size as usize; > > +//! let pagesize_buf =3D LayoutAwareBox::<[u8]>::slice_fill(buf_len, a= lign, 0).unwrap(); > > +//! > > +//! // The alignment of the inner T will be align_of::() in all cas= es, since Rust > > +//! // does not and cannot track alignment: > > +//! assert_eq!(align_of_val(&*pagesize_buf), align_of::()); > > +//! > > +//! // Instead, get it from the layout directly: > > +//! let layout =3D LayoutAwareBox::layout(&pagesize_buf); > > +//! assert_eq!(layout.align(), align); > > +//! ``` > > +//! > > +//! Sometimes you might need to create a zero-sized buffer. This can e= asily be > > +//! done using [`LayoutAwareBox::slice_empty`], which avoids allocatio= ns > > +//! altogether: > > +//! > > +//! ``` > > +//! # use proxmox_alloc::LayoutAwareBox; > > +//! let empty_buf =3D LayoutAwareBox::<[u8]>::slice_empty(); > > +//! ``` > > +//! > > +//! For convenience, you can also convert from a couple existing owned= types: > > +//! > > +//! ``` > > +//! # use proxmox_alloc::LayoutAwareBox; > > +//! let vec: Vec =3D vec![0, 1, 2, 3, 4]; > > +//! let aware_boxed =3D LayoutAwareBox::<[usize]>::from(vec); > > +//! > > +//! assert_eq!(&*aware_boxed, &[0, 1, 2, 3, 4]); > > +//! ``` > > +//! ``` > > +//! # use proxmox_alloc::LayoutAwareBox; > > +//! let boxed: Box<[usize]> =3D Box::new([42, 67]); > > +//! let aware_boxed =3D LayoutAwareBox::<[usize]>::from(boxed); > > +//! > > +//! assert_eq!(&*aware_boxed, &[42, 67]); > > +//! ``` > > +//! ``` > > +//! # use proxmox_alloc::LayoutAwareBox; > > +//! let array: [i32; 4] =3D [0; 4]; > > +//! let aware_boxed =3D LayoutAwareBox::<[i32]>::from(array); > > +//! > > +//! assert_eq!(&*aware_boxed, &[0, 0, 0, 0]); > > +//! ``` > > +//! > > +//! See the [trait implementations](LayoutAwareBox#trait-implementatio= ns) for > > +//! more. > > +//! > > +//! For using `LayoutAwareBox` for custom dynamically sized types, = see > > +//! [below](#custom-dynamically-sized-types). > > +//! > > +//! # Custom Dynamically Sized Types > > +//! > > +//! ***Hint:** If you already know your way around [DSTs][DST] and wan= t to skip > > +//! the explanation, see [here](#full-examples) for complete examples.= * > > +//! > > +//! While [`LayoutAwareBox::slice_fill`] and other methods work great = in cases > > +//! where all you need is a buffer allocated with a custom alignment, = you > > +//! sometimes might have data that comes with a structured part, often= called > > +//! the *header*, and a dynamically sized *payload*. This is relativel= y common > > +//! in the world of networking and file formats. > > +//! > > +//! [`LayoutAwareBox`] cannot help you with the allocation and initial= ization of > > +//! such data, but it can however help you ensure that you do not caus= e any > > +//! [undefined behavior] during deallcations. > > +//! > > +//! In general, you would structure such data as follows: > > +//! > > +//! ``` > > +//! #[repr(C, packed)] > > +//! struct PacketHeader { > > +//! src: [u8; 4], > > +//! dst: [u8; 4], > > +//! } > > +//! > > +//! #[repr(C, packed)] > > +//! struct Packet { > > +//! header: PacketHeader, > > +//! payload: [u8], > > +//! } > > +//! ``` > > +//! > > +//! In this example here, the `payload` is our dynamically sized field= . We > > +//! cannot have more than one dynamically sized field anywhere in our = struct's > > +//! definition, and it must always be at the very end of our struct. T= he > > +//! presence of such a field makes `Packet` a [dynamically sized type > > +//! (DST)][DST]. > > +//! > > +//! We also chose [`#[repr(C, packed)]`][alt-reprs] as our struct's > > +//! representation to avoid any field reordering by the Rust compiler,= and also > > +//! to maximize FFI compatibility. > > +//! > > +//! Note that while it is possible to omit the `PacketHeader` struct e= ntirely > > +//! and instead place `src` and `dst` in `Packet` directly, having a s= eparate > > +//! struct to model the header is useful for size calculations later o= n. > > +//! > > +//! If we then want to allocate a `Packet`, we have to do that by hand= . > > +//! > > +//! First, we need a suitable [`Layout`]. For the purposes of this exa= mple, we > > +//! will choose an arbitrary size and alignment: > > +//! > > +//! ``` > > +//! # #[repr(C, packed)] > > +//! # struct PacketHeader { > > +//! # src: [u8; 4], > > +//! # dst: [u8; 4], > > +//! # } > > +//! # > > +//! # #[repr(C, packed)] > > +//! # struct Packet { > > +//! # header: PacketHeader, > > +//! # payload: [u8], > > +//! # } > > +//! # > > +//! use std::alloc::Layout; > > +//! > > +//! let alloc_size =3D 1024; > > +//! let align =3D 128; > > +//! > > +//! let layout =3D Layout::from_size_align(alloc_size, align).expect("= infallible"); > > +//! ``` > > +//! > > +//! Next, we need to allocate the memory for our `Packet` using our la= yout, > > +//! calling [`handle_alloc_error`](std::alloc::handle_alloc_error) if = our > > +//! allocation failed: > > +//! > > +//! ``` > > +//! # #[repr(C, packed)] > > +//! # struct PacketHeader { > > +//! # src: [u8; 4], > > +//! # dst: [u8; 4], > > +//! # } > > +//! # > > +//! # #[repr(C, packed)] > > +//! # struct Packet { > > +//! # header: PacketHeader, > > +//! # payload: [u8], > > +//! # } > > +//! # > > +//! # use std::alloc::Layout; > > +//! # > > +//! # let alloc_size =3D 1024; > > +//! # let align =3D 128; > > +//! # > > +//! # let layout =3D Layout::from_size_align(alloc_size, align).expect= ("infallible"); > > +//! use std::alloc::alloc_zeroed; > > +//! use std::alloc::handle_alloc_error; > > +//! > > +//! // SAFETY: layout's size is not 0. > > +//! let thin_ptr =3D unsafe { alloc_zeroed(layout) }; > > +//! if thin_ptr.is_null() { > > +//! handle_alloc_error(layout); > > +//! } > > +//! # unsafe { std::alloc::dealloc(thin_ptr, layout) }; > > +//! ``` > > +//! > > +//! `thin_ptr` here is now a `*mut u8`, a raw pointer that points to s= ome bytes. > > +//! We cannot simply cast this to a `*mut Packet` however. Rust actual= ly > > +//! differentiates between two types of pointers: > > +//! > > +//! 1. "Thin" pointers that point to types whose size is known at comp= ile time, > > +//! meaning they implement the [`Sized`] trait. Thin pointers only = consist of > > +//! an address. > > +//! 2. "Fat" pointers that point to types whose size is *not* known at= compile > > +//! time, or in other words, [dynamically sized types][DST]. Fat po= inters > > +//! consist of an address and a length, and are the reason why Rust= knows how > > +//! long a string slice `&str` is, for example. > > +//! > > +//! In fact, the compiler will complain if we try to cast our `thin_pt= r`: > > +//! > > +//! ```compile_fail > > +//! # #[repr(C, packed)] > > +//! # struct PacketHeader { > > +//! # src: [u8; 4], > > +//! # dst: [u8; 4], > > +//! # } > > +//! # > > +//! # #[repr(C, packed)] > > +//! # struct Packet { > > +//! # header: PacketHeader, > > +//! # payload: [u8], > > +//! # } > > +//! # > > +//! # use std::alloc::Layout; > > +//! # > > +//! # let alloc_size =3D 1024; > > +//! # let align =3D 128; > > +//! # > > +//! # let layout =3D Layout::from_size_align(alloc_size, align).expect= ("infallible"); > > +//! # use std::alloc::alloc_zeroed; > > +//! # use std::alloc::handle_alloc_error; > > +//! # > > +//! # // SAFETY: layout's size is not 0. > > +//! # let thin_ptr =3D unsafe { alloc_zeroed(layout) }; > > +//! # if thin_ptr.is_null() { > > +//! # handle_alloc_error(layout); > > +//! # } > > +//! let fat_ptr =3D thin_ptr as *mut Packet; > > +//! ``` > > +//! > > +//! Instead, we must first convert `thin_ptr` to a fat pointer by asso= ciating it > > +//! with a *length*, which we get by subtracting our header's size fro= m the > > +//! number of bytes we allocated. This will make our `*mut u8` a `*mut= [u8]`, > > +//! which we then can cast without the compiler complaining: > > +//! > > +//! ``` > > +//! # #[repr(C, packed)] > > +//! # struct PacketHeader { > > +//! # src: [u8; 4], > > +//! # dst: [u8; 4], > > +//! # } > > +//! # > > +//! # #[repr(C, packed)] > > +//! # struct Packet { > > +//! # header: PacketHeader, > > +//! # payload: [u8], > > +//! # } > > +//! # > > +//! # use std::alloc::Layout; > > +//! # > > +//! # let alloc_size =3D 1024; > > +//! # let align =3D 128; > > +//! # > > +//! # let layout =3D Layout::from_size_align(alloc_size, align).expect= ("infallible"); > > +//! # use std::alloc::alloc_zeroed; > > +//! # use std::alloc::handle_alloc_error; > > +//! # > > +//! # // SAFETY: layout's size is not 0. > > +//! # let thin_ptr =3D unsafe { alloc_zeroed(layout) }; > > +//! # if thin_ptr.is_null() { > > +//! # handle_alloc_error(layout); > > +//! # } > > +//! use std::ptr::slice_from_raw_parts_mut; > > +//! > > +//! let payload_len =3D alloc_size - size_of::(); > > +//! > > +//! let fat_ptr =3D slice_from_raw_parts_mut(thin_ptr, payload_len) as= *mut Packet; > > +//! # unsafe { std::alloc::dealloc(fat_ptr as *mut u8, layout) }; > > +//! ``` > > +//! > > +//! Finally, we can construct our `LayoutAwareBox` by using > > +//! [`LayoutAwareBox::from_raw_parts`]: > > +//! > > +//! ``` > > +//! # #[repr(C, packed)] > > +//! # struct PacketHeader { > > +//! # src: [u8; 4], > > +//! # dst: [u8; 4], > > +//! # } > > +//! # > > +//! # #[repr(C, packed)] > > +//! # struct Packet { > > +//! # header: PacketHeader, > > +//! # payload: [u8], > > +//! # } > > +//! # > > +//! # use std::alloc::Layout; > > +//! # > > +//! # let alloc_size =3D 1024; > > +//! # let align =3D 128; > > +//! # > > +//! # let layout =3D Layout::from_size_align(alloc_size, align).expect= ("infallible"); > > +//! # use std::alloc::alloc_zeroed; > > +//! # use std::alloc::handle_alloc_error; > > +//! # > > +//! # // SAFETY: layout's size is not 0. > > +//! # let thin_ptr =3D unsafe { alloc_zeroed(layout) }; > > +//! # if thin_ptr.is_null() { > > +//! # handle_alloc_error(layout); > > +//! # } > > +//! # use std::ptr::slice_from_raw_parts_mut; > > +//! # > > +//! # let payload_len =3D alloc_size - size_of::(); > > +//! # > > +//! # let fat_ptr =3D slice_from_raw_parts_mut(thin_ptr, payload_len) = as *mut Packet; > > +//! # use proxmox_alloc::LayoutAwareBox; > > +//! // SAFETY: fat_ptr is not aliased or null, is valid for reads and = writes, and its pointee was > > +//! // zero-initialized. Its length is within the bounds of its alloca= tion. > > +//! let aware_boxed =3D unsafe { LayoutAwareBox::from_raw_parts(fat_pt= r, layout) }; > > +//! # assert_eq!((&*aware_boxed).payload.len(), payload_len, "payload.= len() !=3D payload_len"); > > +//! ``` > > +//! > > +//! For a condensed version of this example, as well as examples on ho= w to deal > > +//! with payloads that do not consist of plain bytes, see the [full > > +//! examples](#full-examples) below. > > +//! > > +//! ## Full Examples > > +//! > > +//! A custom [dynamically sized type] with a payload consisting of byt= es: > > +//! > > +//! ``` > > +//! #[repr(C, packed)] > > +//! struct PacketHeader { > > +//! src: [u8; 4], > > +//! dst: [u8; 4], > > +//! } > > +//! > > +//! #[repr(C, packed)] > > +//! struct Packet { > > +//! header: PacketHeader, > > +//! payload: [u8], > > +//! } > > +//! > > +//! # use proxmox_alloc::LayoutAwareBox; > > +//! use std::alloc::Layout; > > +//! use std::alloc::alloc_zeroed; > > +//! use std::alloc::handle_alloc_error; > > +//! use std::ptr::slice_from_raw_parts_mut; > > +//! > > +//! let alloc_size =3D 1024; > > +//! let align =3D 128; > > +//! > > +//! let payload_len =3D alloc_size - size_of::(); > > +//! > > +//! let layout =3D Layout::from_size_align(alloc_size, align).expect("= infallible"); > > +//! > > +//! // SAFETY: layout's size is not 0. > > +//! let thin_ptr =3D unsafe { alloc_zeroed(layout) }; > > +//! if thin_ptr.is_null() { > > +//! handle_alloc_error(layout); > > +//! } > > +//! > > +//! let fat_ptr =3D slice_from_raw_parts_mut(thin_ptr, payload_len) as= *mut Packet; > > +//! > > +//! // SAFETY: fat_ptr is not aliased or null, is valid for reads and = writes, > > +//! // and its pointee was zero-initialized. Its length is within the = bounds > > +//! // of its allocation. > > +//! let aware_boxed =3D unsafe { LayoutAwareBox::from_raw_parts(fat_pt= r, layout) }; > > +//! # assert_eq!((&*aware_boxed).payload.len(), payload_len, "payload.= len() !=3D payload_len"); > > +//! ``` > > +//! > > +//! For payloads that do not consist of plain bytes and instead store = some oddly > > +//! sized type, you must either > > +//! > > +//! 1. shrink the allocation size to the resulting size of your [DST],= or > > +//! 2. manually add padding to your DST's header until it fits your al= location > > +//! > > +//! **In either case, it is important that the size your resulting typ= e is equal > > +//! to that of its allocation.** A mismatch between the data's size an= d the > > +//! allocation's size is considered [undefined behavior] and gets caug= ht by > > +//! [Miri]. > > +//! > > +//! Shrinking the allocation size: > > +//! > > +//! ``` > > +//! #[repr(C, packed)] > > +//! struct PacketHeader { > > +//! src: [u8; 4], > > +//! dst: [u8; 4], > > +//! } > > +//! > > +//! #[repr(C, packed)] > > +//! struct Packet { > > +//! header: PacketHeader, > > +//! payload: [[u8; 7]], > > +//! } > > +//! > > +//! # use proxmox_alloc::LayoutAwareBox; > > +//! use std::alloc::Layout; > > +//! use std::alloc::alloc_zeroed; > > +//! use std::alloc::handle_alloc_error; > > +//! use std::ptr::slice_from_raw_parts_mut; > > +//! > > +//! let max_alloc_size =3D 1024; > > +//! let align =3D 128; > > +//! > > +//! let header_size =3D size_of::(); > > +//! let elem_size =3D size_of::<[u8; 7]>(); > > +//! > > +//! let payload_len =3D (max_alloc_size - header_size) / elem_size; > > +//! > > +//! // Allocated size *must* correspond to the actual size that Packet= will occupy! > > +//! let alloc_size =3D payload_len * elem_size + header_size; > > +//! > > +//! let layout =3D Layout::from_size_align(alloc_size, align).expect("= infallible"); > > +//! > > +//! // SAFETY: layout's size is not 0. > > +//! let thin_ptr =3D unsafe { alloc_zeroed(layout) }; > > +//! if thin_ptr.is_null() { > > +//! handle_alloc_error(layout); > > +//! } > > +//! > > +//! let fat_ptr =3D slice_from_raw_parts_mut(thin_ptr, payload_len) as= *mut Packet; > > +//! > > +//! // SAFETY: fat_ptr is not aliased or null, is valid for reads and = writes, and its pointee was > > +//! // zero-initialized. Its length is within the bounds of its alloca= tion. > > +//! let aware_boxed =3D unsafe { LayoutAwareBox::from_raw_parts(fat_pt= r, layout) }; > > +//! # assert_eq!((&*aware_boxed).payload.len(), payload_len, "payload.= len() !=3D payload_len"); > > +//! ``` > > +//! > > +//! Manually adding padding to the header, which you most likely will = have to > > +//! calculate by hand: > > +//! > > +//! ``` > > +//! #[repr(C, packed)] > > +//! struct PacketHeader { > > +//! src: [u8; 4], > > +//! dst: [u8; 4], > > +//! _padding: [u8; 13], > > +//! } > > +//! > > +//! #[repr(C, packed)] > > +//! struct Packet { > > +//! header: PacketHeader, > > +//! payload: [[u8; 17]], > > +//! } > > +//! > > +//! # use proxmox_alloc::LayoutAwareBox; > > +//! use std::alloc::Layout; > > +//! use std::alloc::alloc_zeroed; > > +//! use std::alloc::handle_alloc_error; > > +//! use std::ptr::slice_from_raw_parts_mut; > > +//! > > +//! let max_alloc_size =3D 1024; > > +//! let align =3D 128; > > +//! > > +//! let header_size =3D size_of::(); > > +//! let elem_size =3D size_of::<[u8; 17]>(); > > +//! > > +//! let payload_len =3D (max_alloc_size - header_size) / elem_size; > > +//! > > +//! // Allocated size *must* correspond to the actual size that Packet= will occupy! > > +//! let alloc_size =3D payload_len * elem_size + header_size; > > +//! assert_eq!(max_alloc_size, alloc_size); > > +//! > > +//! let layout =3D Layout::from_size_align(alloc_size, align).expect("= infallible"); > > +//! > > +//! // SAFETY: layout's size is not 0. > > +//! let thin_ptr =3D unsafe { alloc_zeroed(layout) }; > > +//! if thin_ptr.is_null() { > > +//! handle_alloc_error(layout); > > +//! } > > +//! > > +//! let fat_ptr =3D slice_from_raw_parts_mut(thin_ptr, payload_len) as= *mut Packet; > > +//! > > +//! // SAFETY: fat_ptr is not aliased or null, is valid for reads and = writes, and its pointee was > > +//! // zero-initialized. Its length is within the bounds of its alloca= tion. > > +//! let aware_boxed =3D unsafe { LayoutAwareBox::from_raw_parts(fat_pt= r, layout) }; > > +//! # assert_eq!((&*aware_boxed).payload.len(), payload_len, "payload.= len() !=3D payload_len"); > > +//! ``` > > +//! > > +//! [`Layout`]: std::alloc::Layout > > +//! [`alloc_zeroed`]: std::alloc::alloc_zeroed > > +//! > > +//! [DST]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#dynamic= ally-sized-types-dsts > > +//! [Miri]: https://github.com/rust-lang/miri > > +//! [alignment]: https://en.wikipedia.org/wiki/Data_structure_alignmen= t > > +//! [array]: std::array > > +//! [alt-reprs]: https://doc.rust-lang.org/nomicon/other-reprs.html > > +//! [undefined behavior]: https://doc.rust-lang.org/reference/behavior= -considered-undefined.html > > + > > +use std::alloc; > > +use std::mem; > > +use std::mem::ManuallyDrop; > > +use std::ptr; > > + > > +/// The error type returned by certain methods of [`LayoutAwareBox`]. > > +#[derive(Debug)] > > +pub enum LayoutAwareBoxError { > > + /// Memory allocation failed, which is most likely caused by being= out of memory. > > + OutOfMemory, > > + /// Alignment is zero or not a power of 2. > > + InvalidAlignment, > > + /// The requested amount of elements does not fit into a slice. > > + TooManyElements, > > +} > > + > > +impl LayoutAwareBoxError { > > + const fn as_str(&self) -> &'static str { > > + use LayoutAwareBoxError::*; > > + > > + match self { > > + InvalidAlignment =3D> "invalid alignment - must be a power= of 2", > > + OutOfMemory =3D> "memory allocation failed - out of memory= ?", > > + TooManyElements =3D> "too many elements - requested alloca= tion does not fit into slice", > > + } > > + } > > +} > > + > > +impl std::error::Error for LayoutAwareBoxError {} > > + > > +impl std::fmt::Display for LayoutAwareBoxError { > > + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result= { > > + f.write_str(self.as_str()) > > + } > > +} > > + > > +#[derive(Clone)] > > +enum TypeSize { > > + /// "Known" here means that we could ensure that `T` is `Sized` an= d was not > > + /// allocated with a custom layout. Note that this also applies to > > + /// zero-sized types. > > + /// > > + /// If we have a `[T]` instead, it must have been in a `Box<[T]>` = or > > + /// `Vec` before, or was acquired from a sized source like `&[T= ]`, > > + /// `&mut [T]`, `[T; N]`, and so on. Basically anything where we c= an get the > > + /// length for the underlying fat pointer. > > + Known, > > + /// Anything that is not `Known`, which basically means `T: ?Sized= ` and / or > > + /// `T` was allocated with a custom layout. > > + Dynamic(alloc::Layout), > > +} > > Imo LayoutAwareBox should focus entirely on the Dynamic version, > because Known is just a special case of Dynamic where Layout::for_value > equals the stored Layout. To briefly mention / summarize what we discussed off list: Yeah, the `Known` variant is intended for that case, since it also conveniently handles ZSTs at the same time. But, I might just get rid of this in v2 and just always store a `Layout`, then handle ZSTs on dealloc specifically. I'll have to code it out to see if it's gonna be cleaner -- but I'll strongly consider it in any case. Thanks a lot! > > > + > > +/// A pointer to a type that uniquely owns a heap allocation of type `= T`, while > > +/// also keeping track of the [`Layout`] that was used for the allocat= ion. > > +/// > > +/// See the [module-level documentation][crate::aware_boxed] for more > > +/// information. > > +/// > > +/// [`Layout`]: std::alloc::Layout > > +pub struct LayoutAwareBox { > > + inner: ManuallyDrop>, > > + type_size: TypeSize, > > +} > > + > > +impl Drop for LayoutAwareBox > > +where > > + T: ?Sized, > > +{ > > + fn drop(&mut self) { > > + match self.type_size { > > + TypeSize::Known =3D> { > > + // SAFETY: `self` being dropped, so `self.inner` is no= t used > > + // again afterwards > > + unsafe { ManuallyDrop::drop(&mut self.inner) } > > + } > > + TypeSize::Dynamic(layout) =3D> { > > + // SAFETY: `self` being dropped, so `self.inner` is no= t used > > + // again afterwards > > + let inner =3D unsafe { ManuallyDrop::take(&mut self.in= ner) }; > > + let ptr =3D Box::into_raw(inner); > > + > > + // SAFETY: The value behind ptr is valid for reading &= writing, > > + // properly aligned, and valid to drop; dealloc is cal= led with > > + // layout that has been used for allocation > > + unsafe { > > + ptr::drop_in_place(ptr); > > + alloc::dealloc(ptr as *mut u8, layout); > I would really prefer an explicit `if` check to ensure size is not > zero, even if that is not supposed to happen. I mean, the enum already encodes the information that T is not zero-sized. Then again, if I drop the enum overall in v2, an explicit size check will be necessary here anyhow. So, I'll take this into account for v2 as well, thanks! > > + } > > + } > > + } > > + } > > +} > > + > > +impl LayoutAwareBox > > +where > > + T: ?Sized, > > +{ > > + /// Consumes the passed [`LayoutAwareBox`], returning the under= lying > > + /// pointer and [`Layout`] as a tuple. > > + /// > > + /// # Examples > > + /// > > + /// ``` > > + /// # use proxmox_alloc::LayoutAwareBox; > > + /// let aware_boxed =3D LayoutAwareBox::<[usize]>::from([42, 67, 1= 337]); > > + /// > > + /// let (ptr, layout) =3D LayoutAwareBox::into_raw_parts(aware_box= ed); > > + /// > > + /// // SAFETY: We just got this pointer and layout from into_raw_p= arts. > > + /// let aware_boxed =3D unsafe { LayoutAwareBox::from_raw_parts(pt= r, layout) }; > > + /// ``` > > + /// > > + /// [`Layout`]: std::alloc::Layout > > + pub fn into_raw_parts(mut from: Self) -> (*mut T, alloc::Layout) { > > + // SAFETY: We consume `from`, so `from.inner` is not used agai= n afterwards > > + let boxed =3D unsafe { ManuallyDrop::take(&mut from.inner) }; > > + let ptr =3D Box::into_raw(boxed); > > + > > + let layout =3D Self::layout(&from); > nit: the safety comment should mention that it relies on the fact that > the previous two lines cannot panic. Otherwise we would risk a double > free durning while unwinding. Good point, will add that in v2. Thanks! > > + > > + mem::forget(from); // Prevent LayoutAwareBox::drop() from bein= g called > > + > > + (ptr, layout) > > + } > > + > > + /// Create a new [`LayoutAwareBox`] from a pointer and a [`Layo= ut`]. > > + /// > > + /// Note that if `T` is [zero-sized], no allocation is actually pe= rformed. > > + /// > > + /// # Safety > > + /// > > + /// Improper use of this function can lead to [undefined behavior]= and other > > + /// issues. > > + /// > > + /// In general, the same safety requirements as for [`Box::from_ra= w`] apply > > + /// for this function. > > + /// > > + /// **Additionally,** the caller must also guarantee that the pass= ed pointer > > + /// points to a `T` which was allocated using the passed layout **= and** has > > + /// the same size as its underlying allocation. > > + /// > > + /// The latter in particular is easy to miss: If you allocate `102= 4` bytes > > + /// for a struct that only takes up `1000` bytes for some reason, > > + /// deallocating this struct will *still* be considered undefined = behavior > > + /// and will be spotted by [Miri]. > That reason might be pointer provenance. Once you shrink down the > spacial memory range a pointer is allowed to access, you are not > allowed to extend it back to the full size (see std::ptr module docs). > > Makes me wonder if this is even safe: > ptr::slice_from_raw_parts_mut(thin_ptr, len) as *mut DST; > > I'll have to read up on this. Hm, why would that call not be safe? Assuming the size / len calculations are all correct etc. All that `ptr::slice_from_raw_parts_mut` does is say "there are `len` elements in the (trailing) slice of `T` that `thin_ptr` points to" -- I'm not sure it affects the provenance of the pointer in this case..? Actually, reading up on [provenance] now, there's a paragraph that mentions the following: > The Original Pointer for an allocation has provenance that constrains > the spatial permissions of this pointer to the memory range of the > allocation, and the temporal permissions to the lifetime of the > allocation. Provenance is implicitly inherited by all pointers > transitively derived from the Original Pointer through operations like > offset, borrowing, and pointer casts. The last sentence here I think is key, since *I think* that in this context, `ptr::slice_from_raw_parts_mut` should be considered a pointer cast, since it's a thin-to-fat conversion, only changing the pointer's metadata. I therefore would say that this does not create new provenance, since it only constrains the slice's valid range. Then again, what pointer provenance is is not concretely defined yet anyways, so I'm not sure if it's worth worrying over that... (Also, that pattern for DSTs is well-established in the ecosystem, tbf.) [provenance] https://doc.rust-lang.org/std/ptr/index.html#provenance > > > > + /// > > + /// See [Custom Dynamically Sized Types][custom] for a complete wa= lkthrough > > + /// and examples. > > + /// > > + /// [`Layout`]: std::alloc::Layout > > + /// > > + /// [Miri]: https://github.com/rust-lang/miri > > + /// [custom]: crate::aware_boxed#custom-dynamically-sized-types > > + /// [undefined behavior]: https://doc.rust-lang.org/reference/beha= vior-considered-undefined.html > > + /// [zero-sized]: https://doc.rust-lang.org/nomicon/exotic-sizes.h= tml#zero-sized-types-zsts > > + pub unsafe fn from_raw_parts(ptr: *mut T, layout: alloc::Layout) -= > Self { > > + // SAFETY: The caller guarantees that ptr is non-null, valid f= or reads, > > + // and properly aligned. > > + let pointee_size =3D unsafe { mem::size_of_val(&mut *ptr) }; > > + > > + let type_size =3D if pointee_size =3D=3D 0 { > > + TypeSize::Known > > + } else { > > + TypeSize::Dynamic(layout) > > + }; > > + > > + // SAFETY: Caller guarantees that ptr is non-null, valid for r= eads and > > + // writes, properly aligned, and valid to drop. > > + let boxed =3D unsafe { Box::from_raw(ptr) }; > > + let inner =3D ManuallyDrop::new(boxed); > > + > > + Self { inner, type_size } > > + } > > + > > + /// Returns a copy of the stored [`Layout`]. > > + /// > > + /// # Examples > > + /// > > + /// ``` > > + /// use std::alloc::Layout; > > + /// # use proxmox_alloc::LayoutAwareBox; > > + /// > > + /// let aware_boxed =3D LayoutAwareBox::<[usize]>::from([42, 67]); > > + /// let layout =3D LayoutAwareBox::layout(&aware_boxed); > > + /// > > + /// assert_eq!( > > + /// layout, > > + /// Layout::from_size_align(size_of::() * 2, align_of:= :()).unwrap() > > + /// ); > > + /// ``` > > + /// > > + /// [`Layout`]: std::alloc::Layout > > + pub fn layout(from: &Self) -> alloc::Layout { > > + match from.type_size { > > + TypeSize::Known =3D> alloc::Layout::for_value(&**from.inne= r), > > + TypeSize::Dynamic(layout) =3D> layout, > > + } > > + } > > +} > > + > > +impl LayoutAwareBox { > > + /// Moves `T` onto the heap and allocate its memory with the passe= d > > + /// alignment. > > + /// > > + /// The alignment must always be a power of 2. This also includes = `2^0`, > > + /// which is `1`. > > + /// > > + /// Should the passed alignment be smaller than that of `T`, the a= lignment > > + /// of `T` will be used directly instead. > > + /// > > + /// Note that if `T` is [zero-sized], no allocation is actually pe= rformed. > > + /// > > + /// # Examples > > + /// > > + /// ``` > > + /// # use proxmox_alloc::LayoutAwareBox; > > + /// let aware_box =3D LayoutAwareBox::new(67u8, 1).expect("infalli= ble"); > > + /// ``` > > + /// > > + /// [zero-sized]: https://doc.rust-lang.org/nomicon/exotic-sizes.h= tml#zero-sized-types-zsts > > + pub fn new(value: T, mut alignment: usize) -> Result { > > + if alignment < align_of::() { > > + alignment =3D align_of::(); > > + } > nit: I'd rename the parameter to something like `min_align`. ACK, good point actually. Thanks! > > + > > + if alignment =3D=3D 0 { > > + return Err(LayoutAwareBoxError::InvalidAlignment); > > + } > > + > > + let size =3D size_of::(); > > + if size =3D=3D 0 { > > + return Ok(Self { > > + inner: ManuallyDrop::new(Box::new(value)), > > + type_size: TypeSize::Known, > > + }); > > + } > > + > > + let layout =3D alloc::Layout::from_size_align(size, alignment) > > + .map_err(|_| LayoutAwareBoxError::InvalidAlignment)?; > > + > > + // SAFETY: Requirements on layout are enforced by from_size_al= ign > > + let ptr =3D unsafe { alloc::alloc(layout) as *mut T }; > > + if ptr.is_null() { > > + return Err(LayoutAwareBoxError::OutOfMemory); > > + } > > + > > + // ptr points to uninitialized memory, and is currently not a = valid > > + // instance of T. Therefore, we have to write to it without dr= opping the > > + // invalid pointee it currently stores. `value` must also not = be > > + // dropped. > > + // > > + // SAFETY: ptr is non-null, valid for writes and properly alig= ned, > > + // guaranteed earlier through from_size_align > > + unsafe { ptr.write(value) }; > > + > > + // SAFETY: ptr is non-null, properly sized and aligned, and do= esn't > > + // alias with other pointers -- it is also consumed here, mean= ing it > > + // cannot be used elsewhere. > > + let new =3D unsafe { Self::from_raw_parts(ptr, layout) }; > > + > > + Ok(new) > > + } > > +} > > + > > +impl LayoutAwareBox<[T]> { > > + /// Convenience method to return a `LayoutAwareBox<[T]>` with no e= lements in > > + /// it. This implies that the stored `[T]` is [zero-sized] and has= an > > + /// alignment equal to [`align_of::()`][align_of]. > > + /// > > + /// # Examples > > + /// > > + /// ``` > > + /// # use proxmox_alloc::LayoutAwareBox; > > + /// let aware_boxed =3D LayoutAwareBox::<[usize]>::slice_empty(); > > + /// > > + /// assert_eq!(size_of_val(&*aware_boxed), 0); > > + /// assert_eq!(align_of_val(&*aware_boxed), align_of::()); > > + /// ``` > > + /// > > + /// [zero-sized]: https://doc.rust-lang.org/nomicon/exotic-sizes.h= tml#zero-sized-types-zsts > > + pub fn slice_empty() -> Self { > > + LayoutAwareBox { > > + inner: ManuallyDrop::new(Box::new([])), > > + type_size: TypeSize::Known, > > + } > > + } > nit: `Self::from([])` is more convenient > > + > > + fn slice_zero(len: usize) -> Option { > nit: The name doesn't make it clear that this is for zero-sized > allocations, not for zero-initialized ones as one might expect. Good point, will fix in v2. Thanks! > > + if size_of::() =3D=3D 0 { > > + let mut v =3D Vec::new(); > > + debug_assert_eq!(v.capacity(), usize::MAX); > > + > > + // SAFETY: Vec where T is zero-sized always results in = a vector > > + // with a capacity of usize::MAX. Therefore, the resulting= length > > + // can never be outside the reserved capacity here. > > + unsafe { v.set_len(len) }; > > + > > + return Some(Self { > > + inner: ManuallyDrop::new(Box::from(v)), > > + type_size: TypeSize::Known, > > + }); > > + } > > + > > + if len =3D=3D 0 { > > + return Some(Self::slice_empty()); > > + } > > + > > + None > > + } > > + > > + fn slice_alloc( > > + len: usize, > > + mut alignment: usize, > > + ) -> Result<(ptr::NonNull, alloc::Layout), LayoutAwareBoxError>= { > > + debug_assert_ne!(len, 0, "len =3D=3D 0"); > > + debug_assert_ne!(alignment, 0, "alignment =3D=3D 0"); > nit: why assert when it is a handled error? Ah, this was a remnant from my debugging; I usually sprinkle debug asserts everywhere to keep my own reasoning in check. Mea culpa, will remove these in v2. Thanks! > > + > > + let max_len =3D (isize::MAX as usize) / size_of::(); > > + if len > max_len { > > + return Err(LayoutAwareBoxError::TooManyElements); > > + } > nit: this can be replaced by checked_mul.ok_or, or even saturating_mul, > because Layout::from_size_align takes care of the isize limit. Good point, thanks! Will see what I can do in v2. > > + > > + let alloc_size =3D size_of::() * len; > > + > > + if alignment < align_of::() { > > + alignment =3D align_of::(); > > + } > > + > > + let layout =3D alloc::Layout::from_size_align(alloc_size, alig= nment) > > + .map_err(|_| LayoutAwareBoxError::InvalidAlignment)?; > > + > > + // SAFETY: Layout never has a size of 0. > > + let thin_ptr =3D unsafe { alloc::alloc(layout) as *mut T }; > > + let Some(thin_ptr) =3D ptr::NonNull::new(thin_ptr) else { > > + return Err(LayoutAwareBoxError::OutOfMemory); > > + }; > > + > > + Ok((thin_ptr, layout)) > > + } > > + > > + /// Constructs a `LayoutAwareBox<[T]>` using the given length and = alignment, > > + /// and initializes its elements with the provided value. > > + /// > > + /// This method otherwise has the same behavior as [`LayoutAwareBo= x::new`] > > + /// when it comes to alignment and zero-sized types. > > + /// > > + /// # Examples > > + /// > > + /// ``` > > + /// # use proxmox_alloc::LayoutAwareBox; > > + /// let aware_boxed_buf =3D LayoutAwareBox::<[u8]>::slice_fill(102= 4, 128, 0) > > + /// .expect("infallible"); > > + /// > > + /// assert!(aware_boxed_buf.iter().all(|elem| *elem =3D=3D 0)); > > + /// ``` > > + pub fn slice_fill(len: usize, alignment: usize, value: T) -> Resul= t > > + where > > + T: core::clone::Clone, > > + { > > + if let Some(new) =3D Self::slice_zero(len) { > > + return Ok(new); > > + } > > + > > + let (thin_ptr, layout) =3D Self::slice_alloc(len, alignment)?; > > + > > + for i in 0..len { > > + // SAFETY: thin_ptr is valid for writing and we never go o= ut of > > + // bounds during iteration. > > + unsafe { thin_ptr.add(i).write(value.clone()) }; > > + } > nit: this doesn't drop the previous elements if clone panics [...] Oh, that's true actually. Thanks for pointing this out, will see how I'll fix this in v2. > [...] and there is an unnecessary copy in the last iteration. What exactly do you mean? > > Couldn't this entire function just forward to slice_fill_with? The only > insteresting special case is if `value` was all zeroes. Hmm, I think it could, but I'll have to double-check. > > > + > > + // SAFETY: > > + // * We checked that thin_ptr is not null. > > + // * The number of elements does not exceed max_len. > > + // * thin_ptr is not being aliased by any other pointer. > > + // * The elements of the array behind thin_ptr have been init= ialized. > > + let new =3D unsafe { > > + let thick_ptr =3D core::slice::from_raw_parts_mut(thin_ptr= .as_ptr(), len); > > + Self::from_raw_parts(thick_ptr, layout) > > + }; > > + > > + Ok(new) > > + } > > + > > + /// Constructs a `LayoutAwareBox<[T]>` using the given length and = alignment, > > + /// and initializes its elements by calling the provided function. > > + /// > > + /// This method otherwise has the same behavior as [`LayoutAwareBo= x::new`] > > + /// when it comes to alignment and zero-sized types. > > + /// > > + /// # Examples > > + /// > > + /// ``` > > + /// # use proxmox_alloc::LayoutAwareBox; > > + /// let aware_boxed_buf =3D LayoutAwareBox::<[u8]>::slice_fill_wit= h(1024, 128, Default::default) > > + /// .expect("infallible"); > > + /// > > + /// assert!(aware_boxed_buf.iter().all(|elem| *elem =3D=3D 0)); > > + /// ``` > > + /// > > + /// ``` > > + /// # use proxmox_alloc::LayoutAwareBox; > > + /// let mut counter: usize =3D 0; > > + /// > > + /// let init_func =3D || { > > + /// let current =3D counter; > > + /// counter +=3D 1; > > + /// current > > + /// }; > > + /// > > + /// let aware_boxed =3D LayoutAwareBox::<[usize]>::slice_fill_with= (1024, 128, init_func) > > + /// .expect("infallible"); > > + /// > > + /// assert_eq!(&aware_boxed[0..4], &[0, 1, 2, 3]); > > + /// assert_eq!(aware_boxed.len(), counter); > > + /// assert_eq!(aware_boxed[aware_boxed.len() - 1], counter - 1); > > + /// ``` > > + pub fn slice_fill_with( > > + len: usize, > > + alignment: usize, > > + mut func: F, > > + ) -> Result > > + where > > + F: FnMut() -> T, > > + { > > + if let Some(new) =3D Self::slice_zero(len) { > > + return Ok(new); > > + } > > + > > + let (thin_ptr, layout) =3D Self::slice_alloc(len, alignment)?; > > + > > + for i in 0..len { > > + // SAFETY: thin_ptr is valid for writing and we never go o= ut of > > + // bounds during iteration. > > + unsafe { thin_ptr.add(i).write(func()) }; > > + } > > + > > + // SAFETY: > > + // * We checked that thin_ptr is not null. > > + // * The number of elements does not exceed max_len. > > + // * thin_ptr is not being aliased by any other pointer. > > + // * The elements of the array behind thin_ptr have been init= ialized. > > + let new =3D unsafe { > > + let thick_ptr =3D core::slice::from_raw_parts_mut(thin_ptr= .as_ptr(), len); > > + Self::from_raw_parts(thick_ptr, layout) > > + }; > > + > > + Ok(new) > > + } > > +} > > + > > +impl Default for LayoutAwareBox > > +where > > + T: Default, > > +{ > > + #[inline] > > + fn default() -> Self { > > + Self { > > + inner: Default::default(), > > + type_size: TypeSize::Known, > > + } > > + } > > +} > > + > > +impl Default for LayoutAwareBox<[T]> { > > + #[inline] > > + fn default() -> Self { > > + Self::slice_empty() > > + } > > +} > > + > > +impl Clone for LayoutAwareBox > > +where > > + T: Clone, > > +{ > > + #[inline] > > + fn clone(&self) -> Self { > > + Self { > > + inner: self.inner.clone(), > This doesn't preserve over-alignment and can also change the size, > leading to UB in drop. Ah, good that you spotted this actually, I totally didn't think of this when I wrote this impl... I had thought that since there is no `T: ?Sized` bound that cloning should be fine, but of course you can have a `T` that's sized and over-aligned at the same time, duh. Quite obvious in hindsight. I'll see if there's a neat way to handle this, otherwise I'll just drop this impl, I think. The only interesting case would be to clone DSTs, but that is actually *really* painful to implement (and not worth the effort, IMO). > > + type_size: self.type_size.clone(), > > + } > > + } > [..] 1175 lines I didn't read