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 A752F1FF0AA for ; Fri, 21 Aug 2026 16:03:06 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 618D4215F4; Fri, 21 Aug 2026 16:03:02 +0200 (CEST) From: "Max R. Carrara" To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox v2 01/10] proxmox-alloc: introduce proxmox-alloc with `LayoutAwareBox` type Date: Fri, 21 Aug 2026 16:02:25 +0200 Message-ID: <20260821140238.615302-2-m.carrara@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260821140238.615302-1-m.carrara@proxmox.com> References: <20260821140238.615302-1-m.carrara@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1787320933744 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.618 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: FRPUL33GUVNVXU4FA65A5ZJ64XXNJ5O6 X-Message-ID-Hash: FRPUL33GUVNVXU4FA65A5ZJ64XXNJ5O6 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 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: 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 --- Cargo.toml | 1 + proxmox-alloc/Cargo.toml | 20 + proxmox-alloc/src/aware_boxed.rs | 2196 ++++++++++++++++++++++++++++++ proxmox-alloc/src/lib.rs | 19 + 4 files changed, 2236 insertions(+) create mode 100644 proxmox-alloc/Cargo.toml create mode 100644 proxmox-alloc/src/aware_boxed.rs create mode 100644 proxmox-alloc/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 16e91c94..c00515e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "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 = "proxmox-alloc" +version = "0.1.0" +description = "Proxmox allocation and collections library" + +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +exclude.workspace = true + +[dependencies] + +[dev-dependencies] +libc.workspace = true + +[features] +default = [] 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 heap +//! 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 commonly +//! used as a wrapper around [dynamically sized types (DSTs)][DST] and types +//! that are allocated with a particular [alignment]. +//! +//! The main problem this type addresses it that the Rust compiler does 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 = 1024; +//! let align = 128; +//! +//! let layout = std::alloc::Layout::from_size_align(buf_len, align).unwrap(); +//! +//! let thin_ptr: *mut u8 = unsafe { std::alloc::alloc_zeroed(layout) }; +//! +//! let fat_ptr: *mut [u8] = std::ptr::slice_from_raw_parts_mut(thin_ptr, buf_len); +//! assert!(!fat_ptr.is_null()); +//! +//! let boxed: Box<[u8]> = unsafe { Box::from_raw(fat_ptr) }; +//! +//! drop(boxed); +//! ``` +//! +//! Here we allocate a simple buffer containing 1024 bytes, with an alignment of +//! 128. [`alloc_zeroed`] returns a plain `*mut u8`, which we then convert to a +//! pointer to a slice (or "fat pointer") that is aware of its own size. +//! 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 fancy +//! alignment. The [`Box`] will make sure that our buffer is deallocated +//! correctly once dropped. We make this explicit here by using [`drop`] +//! directly. +//! +//! While our logic might seem sound here, this will actually be reported 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 alignment for the +//! allocation: +//! +//! ``` +//! use proxmox_alloc::LayoutAwareBox; +//! +//! struct SomeData { +//! first: u32, +//! second: u32, +//! } +//! +//! let value = SomeData { first: 10, second: 20 }; +//! +//! let aware_box: LayoutAwareBox = LayoutAwareBox::new(value, 64) +//! .expect("infallible"); +//! ``` +//! +//! Create a page-size-aligned, zero-initialized buffer using +//! [`LayoutAwareBox::slice_fill`]: +//! +//! ``` +//! use libc; +//! +//! use proxmox_alloc::LayoutAwareBox; +//! +//! let buf_len = 2usize.pow(14); +//! +//! let page_size: i64 = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; +//! assert!(page_size > 0, "failed to query PAGESIZE"); +//! +//! let align = page_size as usize; +//! +//! let pagesize_buf = LayoutAwareBox::<[u8]>::slice_fill(buf_len, align, 0).unwrap(); +//! +//! assert_eq!(pagesize_buf.len(), buf_len); +//! ``` +//! +//! The same can also be done by using [`LayoutAwareBox::slice_fill_with`] and +//! passing [`Default::default`]: +//! +//! ``` +//! # use libc; +//! # use proxmox_alloc::LayoutAwareBox; +//! # let buf_len = 2usize.pow(14); +//! # let page_size: i64 = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; +//! # assert!(page_size > 0, "failed to query PAGESIZE"); +//! # let align = page_size as usize; +//! let pagesize_buf = +//! LayoutAwareBox::<[u8]>::slice_fill_with(buf_len, align, Default::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 = 2usize.pow(14); +//! # let page_size: i64 = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; +//! # assert!(page_size > 0, "failed to query PAGESIZE"); +//! let align = page_size as usize; +//! let pagesize_buf = LayoutAwareBox::<[u8]>::slice_fill(buf_len, align, 0).unwrap(); +//! +//! // The alignment of the inner T will be align_of::() in all cases, 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 = LayoutAwareBox::layout(&pagesize_buf); +//! assert_eq!(layout.align(), align); +//! ``` +//! +//! Sometimes you might need to create a zero-sized buffer. This can easily be +//! done using [`LayoutAwareBox::slice_empty`], which avoids allocations +//! altogether: +//! +//! ``` +//! # use proxmox_alloc::LayoutAwareBox; +//! let empty_buf = LayoutAwareBox::<[u8]>::slice_empty(); +//! ``` +//! +//! For convenience, you can also convert from a couple existing owned types: +//! +//! ``` +//! # use proxmox_alloc::LayoutAwareBox; +//! let vec: Vec = vec![0, 1, 2, 3, 4]; +//! let aware_boxed = LayoutAwareBox::<[usize]>::from(vec); +//! +//! assert_eq!(&*aware_boxed, &[0, 1, 2, 3, 4]); +//! ``` +//! ``` +//! # use proxmox_alloc::LayoutAwareBox; +//! let boxed: Box<[usize]> = Box::new([42, 67]); +//! let aware_boxed = LayoutAwareBox::<[usize]>::from(boxed); +//! +//! assert_eq!(&*aware_boxed, &[42, 67]); +//! ``` +//! ``` +//! # use proxmox_alloc::LayoutAwareBox; +//! let array: [i32; 4] = [0; 4]; +//! let aware_boxed = LayoutAwareBox::<[i32]>::from(array); +//! +//! assert_eq!(&*aware_boxed, &[0, 0, 0, 0]); +//! ``` +//! +//! See the [trait implementations](LayoutAwareBox#trait-implementations) 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 want 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 relatively common +//! in the world of networking and file formats. +//! +//! [`LayoutAwareBox`] cannot help you with the allocation and initialization of +//! such data, but it can however help you ensure that you do not cause 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. The +//! 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 entirely +//! and instead place `src` and `dst` in `Packet` directly, having a separate +//! struct to model the header is useful for size calculations later on. +//! +//! 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 example, 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 = 1024; +//! let align = 128; +//! +//! let layout = Layout::from_size_align(alloc_size, align).expect("infallible"); +//! ``` +//! +//! Next, we need to allocate the memory for our `Packet` using our layout, +//! 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 = 1024; +//! # let align = 128; +//! # +//! # let layout = 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 = 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 some bytes. +//! We cannot simply cast this to a `*mut Packet` however. Rust actually +//! differentiates between two types of pointers: +//! +//! 1. "Thin" pointers that point to types whose size is known at compile 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 pointers +//! 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_ptr`: +//! +//! ```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 = 1024; +//! # let align = 128; +//! # +//! # let layout = 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 = unsafe { alloc_zeroed(layout) }; +//! # if thin_ptr.is_null() { +//! # handle_alloc_error(layout); +//! # } +//! let fat_ptr = thin_ptr as *mut Packet; +//! ``` +//! +//! Instead, we must first convert `thin_ptr` to a fat pointer by associating it +//! with a *length*, which we get by subtracting our header's size from 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 = 1024; +//! # let align = 128; +//! # +//! # let layout = 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 = unsafe { alloc_zeroed(layout) }; +//! # if thin_ptr.is_null() { +//! # handle_alloc_error(layout); +//! # } +//! use std::ptr::slice_from_raw_parts_mut; +//! +//! let payload_len = alloc_size - size_of::(); +//! +//! let fat_ptr = 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 = 1024; +//! # let align = 128; +//! # +//! # let layout = 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 = unsafe { alloc_zeroed(layout) }; +//! # if thin_ptr.is_null() { +//! # handle_alloc_error(layout); +//! # } +//! # use std::ptr::slice_from_raw_parts_mut; +//! # +//! # let payload_len = alloc_size - size_of::(); +//! # +//! # let fat_ptr = 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 allocation. +//! let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; +//! # assert_eq!((&*aware_boxed).payload.len(), payload_len, "payload.len() != payload_len"); +//! ``` +//! +//! For a condensed version of this example, as well as examples on how 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 bytes: +//! +//! ``` +//! #[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 = 1024; +//! let align = 128; +//! +//! let payload_len = alloc_size - size_of::(); +//! +//! let layout = Layout::from_size_align(alloc_size, align).expect("infallible"); +//! +//! // SAFETY: layout's size is not 0. +//! let thin_ptr = unsafe { alloc_zeroed(layout) }; +//! if thin_ptr.is_null() { +//! handle_alloc_error(layout); +//! } +//! +//! let fat_ptr = 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 = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; +//! # assert_eq!((&*aware_boxed).payload.len(), payload_len, "payload.len() != 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 allocation +//! +//! **In either case, it is important that the size your resulting type is equal +//! to that of its allocation.** A mismatch between the data's size and the +//! allocation's size is considered [undefined behavior] and gets caught 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 = 1024; +//! let align = 128; +//! +//! let header_size = size_of::(); +//! let elem_size = size_of::<[u8; 7]>(); +//! +//! let payload_len = (max_alloc_size - header_size) / elem_size; +//! +//! // Allocated size *must* correspond to the actual size that Packet will occupy! +//! let alloc_size = payload_len * elem_size + header_size; +//! +//! let layout = Layout::from_size_align(alloc_size, align).expect("infallible"); +//! +//! // SAFETY: layout's size is not 0. +//! let thin_ptr = unsafe { alloc_zeroed(layout) }; +//! if thin_ptr.is_null() { +//! handle_alloc_error(layout); +//! } +//! +//! let fat_ptr = 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 = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; +//! # assert_eq!((&*aware_boxed).payload.len(), payload_len, "payload.len() != 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 = 1024; +//! let align = 128; +//! +//! let header_size = size_of::(); +//! let elem_size = size_of::<[u8; 17]>(); +//! +//! let payload_len = (max_alloc_size - header_size) / elem_size; +//! +//! // Allocated size *must* correspond to the actual size that Packet will occupy! +//! let alloc_size = payload_len * elem_size + header_size; +//! assert_eq!(max_alloc_size, alloc_size); +//! +//! let layout = Layout::from_size_align(alloc_size, align).expect("infallible"); +//! +//! // SAFETY: layout's size is not 0. +//! let thin_ptr = unsafe { alloc_zeroed(layout) }; +//! if thin_ptr.is_null() { +//! handle_alloc_error(layout); +//! } +//! +//! let fat_ptr = 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 = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; +//! # assert_eq!((&*aware_boxed).payload.len(), payload_len, "payload.len() != payload_len"); +//! ``` +//! +//! [`Layout`]: std::alloc::Layout +//! [`alloc_zeroed`]: std::alloc::alloc_zeroed +//! +//! [DST]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#dynamically-sized-types-dsts +//! [Miri]: https://github.com/rust-lang/miri +//! [alignment]: https://en.wikipedia.org/wiki/Data_structure_alignment +//! [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 => "invalid alignment - must be a power of 2", + OutOfMemory => "memory allocation failed - out of memory?", + TooManyElements => "too many elements - requested allocation 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` and 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 can 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), +} + +/// 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 allocation. +/// +/// 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 => { + // SAFETY: `self` being dropped, so `self.inner` is not used + // again afterwards + unsafe { ManuallyDrop::drop(&mut self.inner) } + } + TypeSize::Dynamic(layout) => { + // SAFETY: `self` being dropped, so `self.inner` is not used + // again afterwards + let inner = unsafe { ManuallyDrop::take(&mut self.inner) }; + let ptr = Box::into_raw(inner); + + // SAFETY: The value behind ptr is valid for reading & writing, + // properly aligned, and valid to drop; dealloc is called with + // layout that has been used for allocation + unsafe { + ptr::drop_in_place(ptr); + alloc::dealloc(ptr as *mut u8, layout); + } + } + } + } +} + +impl LayoutAwareBox +where + T: ?Sized, +{ + /// Consumes the passed [`LayoutAwareBox`], returning the underlying + /// pointer and [`Layout`] as a tuple. + /// + /// # Examples + /// + /// ``` + /// # use proxmox_alloc::LayoutAwareBox; + /// let aware_boxed = LayoutAwareBox::<[usize]>::from([42, 67, 1337]); + /// + /// let (ptr, layout) = LayoutAwareBox::into_raw_parts(aware_boxed); + /// + /// // SAFETY: We just got this pointer and layout from into_raw_parts. + /// let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(ptr, 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 again afterwards + let boxed = unsafe { ManuallyDrop::take(&mut from.inner) }; + let ptr = Box::into_raw(boxed); + + let layout = Self::layout(&from); + + mem::forget(from); // Prevent LayoutAwareBox::drop() from being called + + (ptr, layout) + } + + /// Create a new [`LayoutAwareBox`] from a pointer and a [`Layout`]. + /// + /// Note that if `T` is [zero-sized], no allocation is actually performed. + /// + /// # Safety + /// + /// Improper use of this function can lead to [undefined behavior] and other + /// issues. + /// + /// In general, the same safety requirements as for [`Box::from_raw`] apply + /// for this function. + /// + /// **Additionally,** the caller must also guarantee that the passed 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 `1024` 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]. + /// + /// See [Custom Dynamically Sized Types][custom] for a complete walkthrough + /// 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/behavior-considered-undefined.html + /// [zero-sized]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#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 for reads, + // and properly aligned. + let pointee_size = unsafe { mem::size_of_val(&mut *ptr) }; + + let type_size = if pointee_size == 0 { + TypeSize::Known + } else { + TypeSize::Dynamic(layout) + }; + + // SAFETY: Caller guarantees that ptr is non-null, valid for reads and + // writes, properly aligned, and valid to drop. + let boxed = unsafe { Box::from_raw(ptr) }; + let inner = 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 = LayoutAwareBox::<[usize]>::from([42, 67]); + /// let layout = 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 => alloc::Layout::for_value(&**from.inner), + TypeSize::Dynamic(layout) => layout, + } + } +} + +impl LayoutAwareBox { + /// Moves `T` onto the heap and allocate its memory with the passed + /// 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 alignment + /// of `T` will be used directly instead. + /// + /// Note that if `T` is [zero-sized], no allocation is actually performed. + /// + /// # Examples + /// + /// ``` + /// # use proxmox_alloc::LayoutAwareBox; + /// let aware_box = LayoutAwareBox::new(67u8, 1).expect("infallible"); + /// ``` + /// + /// [zero-sized]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts + pub fn new(value: T, mut alignment: usize) -> Result { + if alignment < align_of::() { + alignment = align_of::(); + } + + if alignment == 0 { + return Err(LayoutAwareBoxError::InvalidAlignment); + } + + let size = size_of::(); + if size == 0 { + return Ok(Self { + inner: ManuallyDrop::new(Box::new(value)), + type_size: TypeSize::Known, + }); + } + + let layout = alloc::Layout::from_size_align(size, alignment) + .map_err(|_| LayoutAwareBoxError::InvalidAlignment)?; + + // SAFETY: Requirements on layout are enforced by from_size_align + let ptr = 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 dropping the + // invalid pointee it currently stores. `value` must also not be + // dropped. + // + // SAFETY: ptr is non-null, valid for writes and properly aligned, + // guaranteed earlier through from_size_align + unsafe { ptr.write(value) }; + + // SAFETY: ptr is non-null, properly sized and aligned, and doesn't + // alias with other pointers -- it is also consumed here, meaning it + // cannot be used elsewhere. + let new = unsafe { Self::from_raw_parts(ptr, layout) }; + + Ok(new) + } +} + +impl LayoutAwareBox<[T]> { + /// Convenience method to return a `LayoutAwareBox<[T]>` with no elements 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 = 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.html#zero-sized-types-zsts + pub fn slice_empty() -> Self { + LayoutAwareBox { + inner: ManuallyDrop::new(Box::new([])), + type_size: TypeSize::Known, + } + } + + fn slice_zero(len: usize) -> Option { + if size_of::() == 0 { + let mut v = 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 == 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 == 0"); + debug_assert_ne!(alignment, 0, "alignment == 0"); + + let max_len = (isize::MAX as usize) / size_of::(); + if len > max_len { + return Err(LayoutAwareBoxError::TooManyElements); + } + + let alloc_size = size_of::() * len; + + if alignment < align_of::() { + alignment = align_of::(); + } + + let layout = alloc::Layout::from_size_align(alloc_size, alignment) + .map_err(|_| LayoutAwareBoxError::InvalidAlignment)?; + + // SAFETY: Layout never has a size of 0. + let thin_ptr = unsafe { alloc::alloc(layout) as *mut T }; + let Some(thin_ptr) = 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 [`LayoutAwareBox::new`] + /// when it comes to alignment and zero-sized types. + /// + /// # Examples + /// + /// ``` + /// # use proxmox_alloc::LayoutAwareBox; + /// let aware_boxed_buf = LayoutAwareBox::<[u8]>::slice_fill(1024, 128, 0) + /// .expect("infallible"); + /// + /// assert!(aware_boxed_buf.iter().all(|elem| *elem == 0)); + /// ``` + pub fn slice_fill(len: usize, alignment: usize, value: T) -> Result + where + T: core::clone::Clone, + { + if let Some(new) = Self::slice_zero(len) { + return Ok(new); + } + + let (thin_ptr, layout) = Self::slice_alloc(len, alignment)?; + + for i in 0..len { + // SAFETY: thin_ptr is valid for writing and we never go out of + // bounds during iteration. + unsafe { thin_ptr.add(i).write(value.clone()) }; + } + + // 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 initialized. + let new = unsafe { + let thick_ptr = 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 [`LayoutAwareBox::new`] + /// when it comes to alignment and zero-sized types. + /// + /// # Examples + /// + /// ``` + /// # use proxmox_alloc::LayoutAwareBox; + /// let aware_boxed_buf = LayoutAwareBox::<[u8]>::slice_fill_with(1024, 128, Default::default) + /// .expect("infallible"); + /// + /// assert!(aware_boxed_buf.iter().all(|elem| *elem == 0)); + /// ``` + /// + /// ``` + /// # use proxmox_alloc::LayoutAwareBox; + /// let mut counter: usize = 0; + /// + /// let init_func = || { + /// let current = counter; + /// counter += 1; + /// current + /// }; + /// + /// let aware_boxed = 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) = Self::slice_zero(len) { + return Ok(new); + } + + let (thin_ptr, layout) = Self::slice_alloc(len, alignment)?; + + for i in 0..len { + // SAFETY: thin_ptr is valid for writing and we never go out 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 initialized. + let new = unsafe { + let thick_ptr = 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(), + type_size: self.type_size.clone(), + } + } + + #[inline] + fn clone_from(&mut self, source: &Self) { + (**self).clone_from(&(**source)) + } +} + +impl Clone for LayoutAwareBox<[T]> +where + T: Clone, +{ + #[inline] + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + type_size: self.type_size.clone(), + } + } + + #[inline] + fn clone_from(&mut self, source: &Self) { + *self = source.clone() + } +} + +impl std::ops::Deref for LayoutAwareBox +where + T: ?Sized, +{ + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for LayoutAwareBox +where + T: ?Sized, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl PartialEq for LayoutAwareBox +where + T: PartialEq + ?Sized, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + PartialEq::eq(&**self, &**other) + } + + #[inline] + #[allow(clippy::partialeq_ne_impl)] + fn ne(&self, other: &Self) -> bool { + PartialEq::ne(&**self, &**other) + } +} + +impl Eq for LayoutAwareBox where T: Eq + ?Sized {} + +impl PartialOrd for LayoutAwareBox +where + T: PartialOrd + ?Sized, +{ + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + PartialOrd::partial_cmp(&**self, &**other) + } + + #[inline] + fn lt(&self, other: &Self) -> bool { + PartialOrd::lt(&**self, &**other) + } + + #[inline] + fn le(&self, other: &Self) -> bool { + PartialOrd::le(&**self, &**other) + } + + #[inline] + fn ge(&self, other: &Self) -> bool { + PartialOrd::ge(&**self, &**other) + } + + #[inline] + fn gt(&self, other: &Self) -> bool { + PartialOrd::gt(&**self, &**other) + } +} + +impl Ord for LayoutAwareBox +where + T: Ord + ?Sized, +{ + #[inline] + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + Ord::cmp(&**self, &**other) + } +} + +impl std::hash::Hash for LayoutAwareBox +where + T: std::hash::Hash + ?Sized, +{ + #[inline] + fn hash(&self, state: &mut H) { + std::hash::Hash::hash(&**self, state) + } +} + +impl std::hash::Hasher for LayoutAwareBox +where + T: std::hash::Hasher + ?Sized, +{ + #[inline] + fn finish(&self) -> u64 { + (**self).finish() + } + + #[inline] + fn write(&mut self, bytes: &[u8]) { + (**self).write(bytes) + } + + #[inline] + fn write_u8(&mut self, i: u8) { + (**self).write_u8(i) + } + + #[inline] + fn write_u16(&mut self, i: u16) { + (**self).write_u16(i) + } + + #[inline] + fn write_u32(&mut self, i: u32) { + (**self).write_u32(i) + } + + #[inline] + fn write_u64(&mut self, i: u64) { + (**self).write_u64(i) + } + + #[inline] + fn write_u128(&mut self, i: u128) { + (**self).write_u128(i) + } + + #[inline] + fn write_usize(&mut self, i: usize) { + (**self).write_usize(i) + } + + #[inline] + fn write_i8(&mut self, i: i8) { + (**self).write_i8(i) + } + + #[inline] + fn write_i16(&mut self, i: i16) { + (**self).write_i16(i) + } + + #[inline] + fn write_i32(&mut self, i: i32) { + (**self).write_i32(i) + } + + #[inline] + fn write_i64(&mut self, i: i64) { + (**self).write_i64(i) + } + + #[inline] + fn write_i128(&mut self, i: i128) { + (**self).write_i128(i) + } + + #[inline] + fn write_isize(&mut self, i: isize) { + (**self).write_isize(i) + } +} + +impl std::fmt::Display for LayoutAwareBox +where + T: std::fmt::Display + ?Sized, +{ + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&**self, f) + } +} + +impl std::fmt::Debug for LayoutAwareBox +where + T: std::fmt::Debug + ?Sized, +{ + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&**self, f) + } +} + +impl std::fmt::Pointer for LayoutAwareBox +where + T: std::fmt::Pointer + ?Sized, +{ + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Pointer::fmt(&**self, f) + } +} + +impl core::borrow::Borrow for LayoutAwareBox +where + T: ?Sized, +{ + #[inline] + fn borrow(&self) -> &T { + self + } +} + +impl core::borrow::BorrowMut for LayoutAwareBox +where + T: ?Sized, +{ + #[inline] + fn borrow_mut(&mut self) -> &mut T { + self + } +} + +impl core::convert::AsRef for LayoutAwareBox +where + T: ?Sized, +{ + #[inline] + fn as_ref(&self) -> &T { + self + } +} + +impl core::convert::AsMut for LayoutAwareBox +where + T: ?Sized, +{ + #[inline] + fn as_mut(&mut self) -> &mut T { + self + } +} + +impl core::error::Error for LayoutAwareBox +where + E: core::error::Error, +{ + #[inline] + fn cause(&self) -> Option<&dyn std::error::Error> { + core::error::Error::source(&**self) + } + + #[inline] + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + core::error::Error::source(&**self) + } +} + +impl<'a, T> IntoIterator for &'a LayoutAwareBox<[T]> { + type Item = &'a T; + + type IntoIter = core::slice::Iter<'a, T>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl FromIterator for LayoutAwareBox<[I]> { + #[inline] + fn from_iter>(iter: T) -> Self { + Self { + inner: ManuallyDrop::new(Box::from_iter(iter)), + type_size: TypeSize::Known, + } + } +} + +impl Iterator for LayoutAwareBox +where + I: Iterator + ?Sized, +{ + type Item = I::Item; + + #[inline] + fn next(&mut self) -> Option { + (**self).next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + (**self).size_hint() + } + + #[inline] + fn nth(&mut self, n: usize) -> Option { + (**self).nth(n) + } +} + +impl DoubleEndedIterator for LayoutAwareBox +where + I: DoubleEndedIterator + ?Sized, +{ + #[inline] + fn next_back(&mut self) -> Option { + (**self).next_back() + } + + #[inline] + fn nth_back(&mut self, n: usize) -> Option { + (**self).nth_back(n) + } +} +impl ExactSizeIterator for LayoutAwareBox +where + I: ExactSizeIterator + ?Sized, +{ + #[inline] + fn len(&self) -> usize { + (**self).len() + } +} + +impl std::iter::FusedIterator for LayoutAwareBox where I: std::iter::FusedIterator + ?Sized {} + +impl std::io::Read for LayoutAwareBox +where + R: std::io::Read + ?Sized, +{ + #[inline] + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + (**self).read(buf) + } + + #[inline] + fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result { + (**self).read_vectored(bufs) + } + + #[inline] + fn read_to_end(&mut self, buf: &mut Vec) -> std::io::Result { + (**self).read_to_end(buf) + } + + #[inline] + fn read_to_string(&mut self, buf: &mut String) -> std::io::Result { + (**self).read_to_string(buf) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { + (**self).read_exact(buf) + } +} + +impl std::io::Write for LayoutAwareBox +where + W: std::io::Write + ?Sized, +{ + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + (**self).write(buf) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result { + (**self).write_vectored(bufs) + } + + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + (**self).flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { + (**self).write_all(buf) + } + + #[inline] + fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> std::io::Result<()> { + (**self).write_fmt(fmt) + } +} + +impl std::io::Seek for LayoutAwareBox +where + S: std::io::Seek + ?Sized, +{ + #[inline] + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + (**self).seek(pos) + } + + #[inline] + fn rewind(&mut self) -> std::io::Result<()> { + (**self).rewind() + } + + #[inline] + fn stream_position(&mut self) -> std::io::Result { + (**self).stream_position() + } + + #[inline] + fn seek_relative(&mut self, offset: i64) -> std::io::Result<()> { + (**self).seek_relative(offset) + } +} + +impl std::io::BufRead for LayoutAwareBox +where + B: std::io::BufRead + ?Sized, +{ + #[inline] + fn fill_buf(&mut self) -> std::io::Result<&[u8]> { + (**self).fill_buf() + } + + #[inline] + fn consume(&mut self, amt: usize) { + (**self).consume(amt) + } + + #[inline] + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> std::io::Result { + (**self).read_until(byte, buf) + } + + #[inline] + fn skip_until(&mut self, byte: u8) -> std::io::Result { + (**self).skip_until(byte) + } + + #[inline] + fn read_line(&mut self, buf: &mut String) -> std::io::Result { + (**self).read_line(buf) + } +} + +// NOTE: The `From` conversion impls below are all "trivial" in the sense that +// each `Box` we store in `Self` can drop and deallocate its inner value just +// fine. This is because we do not use `T: ?Sized`, so therefore we may use +// `TypeSize::Known`. + +impl From for LayoutAwareBox { + #[inline] + fn from(value: T) -> Self { + Self { + inner: ManuallyDrop::new(Box::from(value)), + type_size: TypeSize::Known, + } + } +} + +impl From> for LayoutAwareBox { + #[inline] + fn from(value: Box) -> Self { + Self { + inner: ManuallyDrop::new(value), + type_size: TypeSize::Known, + } + } +} + +impl From> for LayoutAwareBox<[T]> { + #[inline] + fn from(value: Box<[T]>) -> Self { + Self { + inner: ManuallyDrop::new(value), + type_size: TypeSize::Known, + } + } +} + +impl From> for LayoutAwareBox<[T]> { + #[inline] + fn from(value: Vec) -> Self { + Self { + inner: ManuallyDrop::new(Box::from(value)), + type_size: TypeSize::Known, + } + } +} + +impl From<[T; N]> for LayoutAwareBox<[T]> { + #[inline] + fn from(value: [T; N]) -> Self { + Self { + inner: ManuallyDrop::new(Box::from(value)), + type_size: TypeSize::Known, + } + } +} + +impl From<&[T]> for LayoutAwareBox<[T]> +where + T: Clone, +{ + /// This conversion allocates on the heap and performs a copy of `&[T]` and + /// its contents. + #[inline] + fn from(value: &[T]) -> Self { + Self { + inner: ManuallyDrop::new(Box::from(value)), + type_size: TypeSize::Known, + } + } +} +impl From<&mut [T]> for LayoutAwareBox<[T]> +where + T: Clone, +{ + /// This conversion allocates on the heap and performs a copy of `&mut [T]` + /// and its contents. + #[inline] + fn from(value: &mut [T]) -> Self { + Self { + inner: ManuallyDrop::new(Box::from(value)), + type_size: TypeSize::Known, + } + } +} + +impl From> for LayoutAwareBox<[T]> +where + T: Clone, +{ + /// When cow is the [`Cow::Borrowed`](std::borrow::Cow::Borrowed) variant, + /// this conversion allocates on the heap and copies the underlying slice. + /// Otherwise, it will try to reuse the owned [`Vec`]’s allocation. + #[inline] + fn from(value: std::borrow::Cow<'_, [T]>) -> Self { + Self { + inner: ManuallyDrop::new(Box::from(value)), + type_size: TypeSize::Known, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn zero_sized() { + let boxed_unit = LayoutAwareBox::new((), 1).expect("infallible"); + let inner_ref: &() = &*boxed_unit; + + assert_eq!(size_of_val(inner_ref), 0, "size_of_val() != 0"); + assert_eq!(align_of_val(inner_ref), 1, "align_of_val() != 1"); + + drop(boxed_unit); + + let empty_slice = LayoutAwareBox::<[u8]>::slice_empty(); + let inner_ref: &[u8] = &*empty_slice; + + assert_eq!(inner_ref.len(), 0, "len != 0"); + assert_eq!(size_of_val(inner_ref), 0, "size_of_val() != 0"); + assert_eq!(align_of_val(inner_ref), 1, "align_of_val() != 1"); + + drop(empty_slice); + + let empty_unit_slice = LayoutAwareBox::<[()]>::slice_empty(); + let inner_ref: &[()] = &*empty_unit_slice; + + assert_eq!(inner_ref.len(), 0, "len != 0"); + assert_eq!(size_of_val(inner_ref), 0, "size_of_val() != 0"); + assert_eq!(align_of_val(inner_ref), 1, "align_of_val() != 1"); + + drop(empty_unit_slice); + + let filled_unit_slice = LayoutAwareBox::<[()]>::slice_fill(13, 1, ()).expect("infallible"); + let inner_ref: &[()] = &*filled_unit_slice; + + assert_eq!(inner_ref.len(), 13, "len != 13"); + assert_eq!(size_of_val(inner_ref), 0, "size_of_val() != 0"); + assert_eq!(align_of_val(inner_ref), 1, "align_of_val() != 1"); + + for element in &filled_unit_slice { + assert_eq!(*element, ()); + } + + assert_eq!(filled_unit_slice.into_iter().len(), 13); + + drop(filled_unit_slice); + } + + #[test] + fn zero_sized_layout() { + let aware_boxed = LayoutAwareBox::new((), 1).expect("infallible"); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::for_value(&()), + ); + } + + #[test] + fn conversion() { + let vec: Vec = vec![0, 1, 2, 3, 4, 5]; + let aware_boxed = LayoutAwareBox::<[usize]>::from(vec); + + assert_eq!(aware_boxed.len(), 6); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align( + size_of::() * aware_boxed.len(), + align_of::() + ) + .unwrap(), + "layout of slice from vector should have size = size_of::() * len, align = align_of::()" + ); + + drop(aware_boxed); + + let boxed: Box = Box::new(42); + let aware_boxed = LayoutAwareBox::from(boxed); + + assert_eq!( + aware_boxed, + LayoutAwareBox::new(42, align_of::()).expect("infallible"), + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align(size_of::(), align_of::()).unwrap(), + "layout of value from Box should always have size = size_of::(), align = align_of::()" + ); + + drop(aware_boxed); + + let boxed: Box<[usize]> = Box::new([42, 67]); + let aware_boxed = LayoutAwareBox::<[usize]>::from(boxed); + + assert_eq!(aware_boxed, LayoutAwareBox::from(vec![42usize, 67])); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align( + size_of::() * aware_boxed.len(), + align_of::() + ) + .unwrap(), + "layout of slice from Box should always have size = size_of::() * len, align = align_of::()" + ); + + drop(aware_boxed); + + let aware_boxed = LayoutAwareBox::<[usize]>::from([42, 67]); + + assert_eq!(aware_boxed, LayoutAwareBox::from(vec![42usize, 67])); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align( + size_of::() * aware_boxed.len(), + align_of::() + ) + .unwrap(), + "layout of slice from [T; N] should always have size = size_of::() * N, align = align_of::()" + ); + + drop(aware_boxed); + } + + #[test] + fn slice_fill_sized_nonempty() { + let aware_boxed = + LayoutAwareBox::slice_fill(7, align_of::(), 8u32).expect("infallible"); + let inner_ref = &*aware_boxed; + + assert_eq!(inner_ref.len(), 7, "inner_ref.len() != 7"); + + assert_eq!( + align_of_val(inner_ref), + align_of::(), + concat!( + "for any [T] where T: Sized, align_of_val() is always align_of::(),", + " because the compiler does not track alignment" + ) + ); + + assert_eq!( + size_of_val(inner_ref), + size_of::() * 7, + "[T] where T: Sized with N elements should always have a size of size_of::() * N" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align( + size_of::() * aware_boxed.len(), + align_of::() + ) + .unwrap(), + "[T] where T: Sized with N elements should always have a layout where size = size_of::() * N and the alignment that was specified" + ); + + assert_eq!(inner_ref, &[8u32; 7], "equality sanity check failed"); + + assert_eq!( + aware_boxed, + LayoutAwareBox::slice_fill(7, align_of::(), 8u32).expect("infallible"), + "equality sanity check failed" + ); + + let aware_boxed_other = + LayoutAwareBox::slice_fill_with(7, align_of::(), || 8u32).expect("infallible"); + + assert_eq!( + aware_boxed, aware_boxed_other, + "equality check with aware box from slice_fill_with failed" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&aware_boxed_other), + "layout equality check with aware box from slice_fill_with failed" + ); + + drop(aware_boxed); + } + + #[test] + fn slice_fill_sized_empty() { + let aware_boxed = LayoutAwareBox::slice_fill(0, 1024, 1337usize).expect("infallible"); + let inner_ref = &*aware_boxed; + + assert_eq!(inner_ref.len(), 0, "inner_ref.len() != 0"); + + assert_eq!( + size_of_val(inner_ref), + 0, + "[T] where T: Sized with 0 elements should always have a size of 0" + ); + + assert_eq!( + align_of_val(inner_ref), + align_of::(), + "[T] where T: Sized with 0 elements should always have an alignment of align_of::()" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align(0, align_of::()).unwrap(), + "[T] where T: Sized with 0 elements should always have a layout where size = 0, align = align_of::()" + ); + + assert_eq!(inner_ref, &[], "equality sanity check failed"); + + assert_eq!( + aware_boxed, + LayoutAwareBox::slice_empty(), + "equality sanity check failed" + ); + + let aware_boxed_other = + LayoutAwareBox::slice_fill_with(0, 1024, || 1337usize).expect("infallible"); + + assert_eq!( + aware_boxed, aware_boxed_other, + "equality check with aware box from slice_fill_with failed" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&aware_boxed_other), + "layout equality check with aware box from slice_fill_with failed" + ); + + drop(aware_boxed); + } + + #[test] + fn slice_fill_unsized_nonempty() { + let aware_boxed = LayoutAwareBox::slice_fill(13, 1024, ()).expect("infallible"); + let inner_ref = &*aware_boxed; + + assert_eq!(inner_ref.len(), 13, "inner_ref.len() != 13"); + + assert_eq!( + size_of_val(inner_ref), + 0, + "nonempty slice of a zero-sized type should not have any size" + ); + + assert_eq!( + align_of_val(inner_ref), + 1, + "nonempty slice of a zero-sized type should always have an alignment of 1" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align(0, align_of::<()>()).unwrap(), + "layout of nonempty [T] where T is zero-sized should always have size = 0 and align = 1" + ); + + assert_eq!(inner_ref, &[(); 13], "equality sanity check failed"); + + assert_eq!( + aware_boxed, + LayoutAwareBox::slice_fill(13, 32, ()).expect("infallible"), + "equality sanity check failed" + ); + + let aware_boxed_other = + LayoutAwareBox::slice_fill_with(13, 1024, || ()).expect("infallible"); + + assert_eq!( + aware_boxed, aware_boxed_other, + "equality check with aware box from slice_fill_with failed" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&aware_boxed_other), + "layout equality check with aware box from slice_fill_with failed" + ); + + drop(aware_boxed); + } + + #[test] + fn slice_fill_unsized_empty() { + let aware_boxed = LayoutAwareBox::slice_fill(0, 1024, ()).expect("infallible"); + let inner_ref = &*aware_boxed; + + assert_eq!(inner_ref.len(), 0, "inner_ref.len() != 0"); + + assert_eq!( + size_of_val(inner_ref), + 0, + "empty slice of a zero-sized type should not have any size" + ); + + assert_eq!( + align_of_val(inner_ref), + 1, + "empty slice of a zero-sized type should always have an alignment of 1" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + std::alloc::Layout::from_size_align(0, align_of::<()>()).unwrap(), + "layout of empty [T] where T is zero-sized should always have size = 0 and align = 1" + ); + + assert_eq!(inner_ref, &[(); 0], "equality sanity check failed"); + + assert_eq!( + aware_boxed, + LayoutAwareBox::slice_fill(0, 32, ()).expect("infallible"), + "equality sanity check failed" + ); + + let aware_boxed_other = + LayoutAwareBox::slice_fill_with(0, 1024, || ()).expect("infallible"); + + assert_eq!( + aware_boxed, aware_boxed_other, + "equality check with aware box from slice_fill_with failed" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&aware_boxed_other), + "layout equality check with aware box from slice_fill_with failed" + ); + + drop(aware_boxed); + } + + #[test] + fn from_raw_parts_unsized_slice() { + let len = 11; + let size = size_of::() * len; + let align = align_of::(); + + let layout = std::alloc::Layout::from_size_align(size, align).expect("infallible"); + + let thin_ptr = unsafe { std::alloc::alloc_zeroed(layout) }; + assert!(!thin_ptr.is_null(), "thin_ptr is null"); + + let fat_ptr = std::ptr::slice_from_raw_parts_mut(thin_ptr, len); + assert!(!fat_ptr.is_null(), "fat_ptr is null"); + + let aware_box = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; + + let inner_ref = &*aware_box; + + assert_eq!(inner_ref.len(), len, "inner_ref.len() != len"); + assert_eq!(size_of_val(inner_ref), len, "size_of_val(inner_ref) != len"); + } + + #[test] + fn from_raw_parts_unsized_type() { + #[repr(C, packed)] + struct MyDSTHeader { + foo: usize, + bar: i32, + } + + #[repr(C, packed)] + struct MyDST { + header: MyDSTHeader, + data: [u8], + } + + // -- construction + let alloc_size = 1024; + let header_size = size_of::(); + let data_len = alloc_size - header_size; + + let layout = std::alloc::Layout::from_size_align(alloc_size, align_of::()) + .expect("infallible"); + + let thin_ptr = unsafe { std::alloc::alloc_zeroed(layout) }; + assert!(!thin_ptr.is_null(), "thin_ptr is null"); + + let fat_ptr = std::ptr::slice_from_raw_parts_mut(thin_ptr, data_len) as *mut MyDST; + assert!(!fat_ptr.is_null(), "fat_ptr is null"); + + let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; + let inner_ref = &*aware_boxed; + + // -- actual checks start here + assert_eq!(inner_ref.data.len(), data_len, "data.len() != data_len"); + + assert_eq!( + inner_ref.data.iter().count(), + data_len, + "data.iter().count() != data_len" + ); + + let got_size = size_of_val(inner_ref); + let expected_size = header_size + data_len; + assert_eq!( + got_size, expected_size, + "size_of_val() != header_size + data_len ({got_size} != {expected_size})" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + layout, + "layout equality sanity check failed" + ); + } + + #[test] + fn from_raw_parts_unsized_type_weird_data() { + #[repr(C, packed)] + struct MyDSTHeader { + foo: usize, + bar: i32, + } + + #[repr(Rust)] + #[allow(unused)] + struct MyPayloadElement([u8; 7]); + + #[repr(C, packed)] + struct MyDST { + header: MyDSTHeader, + data: [MyPayloadElement], + } + + // -- construction + let max_alloc_size = 1024; + let header_size = size_of::(); + + let data_element_size = size_of::(); + let data_len = (max_alloc_size - header_size) / data_element_size; + + // Allocated size *must* correspond to actual size that MyDST will occupy + let alloc_size = data_len * data_element_size + header_size; + + let layout = std::alloc::Layout::from_size_align(alloc_size, align_of::()) + .expect("infallible"); + + let thin_ptr = unsafe { std::alloc::alloc_zeroed(layout) }; + assert!(!thin_ptr.is_null(), "thin_ptr is null"); + + let fat_ptr = std::ptr::slice_from_raw_parts_mut(thin_ptr, data_len) as *mut MyDST; + assert!(!fat_ptr.is_null(), "fat_ptr is null"); + + let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; + let inner_ref = &*aware_boxed; + + // -- actual checks start here + assert_eq!(inner_ref.data.len(), data_len, "data.len() != data_len"); + + assert_eq!( + inner_ref.data.iter().count(), + data_len, + "data.iter().count() != data_len" + ); + + let got_size = size_of_val(inner_ref); + let expected_size = header_size + data_element_size * data_len; + assert_eq!( + got_size, expected_size, + "size_of_val() != header_size + data_element_size * data_len \ + ({got_size} != {expected_size})" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + layout, + "layout equality sanity check failed" + ); + } + + #[test] + fn from_raw_parts_unsized_type_weird_data_padded() { + #[repr(C, packed)] + struct MyDSTHeader { + foo: usize, + bar: i32, + _pad1: u8, + _pad2: u8, + _pad3: u8, + _pad4: u8, + } + + #[repr(Rust)] + #[allow(unused)] + struct MyPayloadElement([u8; 7]); + + #[repr(C, packed)] + struct MyDST { + header: MyDSTHeader, + data: [MyPayloadElement], + } + + // -- construction + let max_alloc_size = 1024; + let header_size = size_of::(); + + let data_element_size = size_of::(); + let data_len = (max_alloc_size - header_size) / data_element_size; + + // Allocated size *must* correspond to actual size that MyDST will occupy + let alloc_size = data_len * data_element_size + header_size; + + assert_eq!( + max_alloc_size, alloc_size, + "MyDST will not occupy all {max_alloc_size} allocated bytes after construction" + ); + + let layout = std::alloc::Layout::from_size_align(alloc_size, align_of::()) + .expect("infallible"); + + let thin_ptr = unsafe { std::alloc::alloc_zeroed(layout) }; + assert!(!thin_ptr.is_null(), "thin_ptr is null"); + + let fat_ptr = std::ptr::slice_from_raw_parts_mut(thin_ptr, data_len) as *mut MyDST; + assert!(!fat_ptr.is_null(), "fat_ptr is null"); + + let aware_boxed = unsafe { LayoutAwareBox::from_raw_parts(fat_ptr, layout) }; + let inner_ref = &*aware_boxed; + + // -- actual checks start here + assert_eq!(inner_ref.data.len(), data_len, "data.len() != data_len"); + + assert_eq!( + inner_ref.data.iter().count(), + data_len, + "data.iter().count() != data_len" + ); + + let got_size = size_of_val(inner_ref); + let expected_size = header_size + data_element_size * data_len; + assert_eq!( + got_size, expected_size, + "size_of_val() != header_size + data_element_size * data_len \ + ({got_size} != {expected_size})" + ); + + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + layout, + "layout equality sanity check failed" + ); + } + + #[test] + fn clone_impl() { + let aware_boxed = LayoutAwareBox::::default(); + let cloned = aware_boxed.clone(); + + assert_eq!(aware_boxed, cloned); + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&cloned) + ); + assert_ne!(&*aware_boxed as *const _, &*cloned as *const _); + + drop(aware_boxed); + drop(cloned); + + let aware_boxed = LayoutAwareBox::<[usize]>::default(); + let cloned = aware_boxed.clone(); + + assert_eq!(aware_boxed, cloned); + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&cloned) + ); + assert_eq!( + &*aware_boxed as *const _, &*cloned as *const _, + "empty slices are zero-sized types and thus will always have the same pointer" + ); + + drop(aware_boxed); + drop(cloned); + + let aware_boxed = LayoutAwareBox::<[usize]>::slice_fill(4, 64, 1337).expect("infallible"); + let cloned = aware_boxed.clone(); + + assert_eq!(aware_boxed, cloned); + assert_eq!( + LayoutAwareBox::layout(&aware_boxed), + LayoutAwareBox::layout(&cloned) + ); + assert_ne!(&*aware_boxed as *const _, &*cloned as *const _); + + drop(aware_boxed); + drop(cloned); + } +} diff --git a/proxmox-alloc/src/lib.rs b/proxmox-alloc/src/lib.rs new file mode 100644 index 00000000..64c1beb4 --- /dev/null +++ b/proxmox-alloc/src/lib.rs @@ -0,0 +1,19 @@ +//! # The Proxmox allocation and collections library +//! +//! This library provides smart pointers and collections for managing +//! heap-allocated values, similar to [`core::alloc`]. +//! +//! ## Layout-Aware Box +//! +//! The [`LayoutAwareBox`] is a smart pointer type that, unlike the regular +//! [`Box`], also tracks which [`Layout`] was used to allocate its contents. +//! This is useful for managing the allocations of [dynamically sized types +//! (DSTs)][DST] and creating heap-allocated buffers with a certain alignment. +//! +//! [`Layout`]: std::alloc::Layout +//! +//! [DST]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#dynamically-sized-types-dsts + +pub mod aware_boxed; +pub use aware_boxed::LayoutAwareBox; +pub use aware_boxed::LayoutAwareBoxError; -- 2.47.3