From: "Max R. Carrara" <m.carrara@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [PATCH proxmox v2 01/10] proxmox-alloc: introduce proxmox-alloc with `LayoutAwareBox<T>` type
Date: Fri, 21 Aug 2026 16:02:25 +0200 [thread overview]
Message-ID: <20260821140238.615302-2-m.carrara@proxmox.com> (raw)
In-Reply-To: <20260821140238.615302-1-m.carrara@proxmox.com>
Introducte a new crate called `proxmox-alloc`, an allocation and
collections library similar to `core::alloc`.
Add `LayoutAwareBox<T>`, 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::<T>().
`LayoutAwareBox<T>` solves this issue by tracking which layout was
used during T's allocation, passing it to `dealloc()` in its `drop`
handler.
While `LayoutAwareBox<T>` 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<T>` 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<T>`, 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<String> for LayoutAwareBox<str>`,
`Clone for LayoutAwareBox<CStr>`,
`From<PathBuf> for LayoutAwareBox<Path>`, and so on
- traits that require non-trivial memory manipulation, such as
`Clone for LayoutAwareBox<T> where T: ?Sized`
--> cloning the memory inside a `LayoutAwareBox<T>` itself would be
trivial, but if `T: ?Sized`, it cannot be `Clone`.
Note that `Box<T>` 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<T>` 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<T>` 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 <m.carrara@proxmox.com>
---
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<T>` type for more involved heap allocations.
+//!
+//! `LayoutAwareBox<T>` 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<T>`](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<SomeData> = 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::<T>() in all cases, since Rust
+//! // does not and cannot track alignment:
+//! assert_eq!(align_of_val(&*pagesize_buf), align_of::<u8>());
+//!
+//! // 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<usize> = 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<T>` 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::<PacketHeader>();
+//!
+//! 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<Packet>` 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::<PacketHeader>();
+//! #
+//! # 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::<PacketHeader>();
+//!
+//! 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::<PacketHeader>();
+//! 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::<PacketHeader>();
+//! 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<T>` 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<T: ?Sized> {
+ inner: ManuallyDrop<Box<T>>,
+ type_size: TypeSize,
+}
+
+impl<T> Drop for LayoutAwareBox<T>
+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<T> LayoutAwareBox<T>
+where
+ T: ?Sized,
+{
+ /// Consumes the passed [`LayoutAwareBox<T>`], 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<T>`] 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::<usize>() * 2, align_of::<usize>()).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<T> LayoutAwareBox<T> {
+ /// 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<Self, LayoutAwareBoxError> {
+ if alignment < align_of::<T>() {
+ alignment = align_of::<T>();
+ }
+
+ if alignment == 0 {
+ return Err(LayoutAwareBoxError::InvalidAlignment);
+ }
+
+ let size = size_of::<T>();
+ 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<T> 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::<T>()`][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::<usize>());
+ /// ```
+ ///
+ /// [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<Self> {
+ if size_of::<T>() == 0 {
+ let mut v = Vec::new();
+ debug_assert_eq!(v.capacity(), usize::MAX);
+
+ // SAFETY: Vec<T> 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<T>, 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::<T>();
+ if len > max_len {
+ return Err(LayoutAwareBoxError::TooManyElements);
+ }
+
+ let alloc_size = size_of::<T>() * len;
+
+ if alignment < align_of::<T>() {
+ alignment = align_of::<T>();
+ }
+
+ 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<Self, LayoutAwareBoxError>
+ 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<F>(
+ len: usize,
+ alignment: usize,
+ mut func: F,
+ ) -> Result<Self, LayoutAwareBoxError>
+ 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<T> Default for LayoutAwareBox<T>
+where
+ T: Default,
+{
+ #[inline]
+ fn default() -> Self {
+ Self {
+ inner: Default::default(),
+ type_size: TypeSize::Known,
+ }
+ }
+}
+
+impl<T> Default for LayoutAwareBox<[T]> {
+ #[inline]
+ fn default() -> Self {
+ Self::slice_empty()
+ }
+}
+
+impl<T> 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).clone_from(&(**source))
+ }
+}
+
+impl<T> 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<T> std::ops::Deref for LayoutAwareBox<T>
+where
+ T: ?Sized,
+{
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+}
+
+impl<T> std::ops::DerefMut for LayoutAwareBox<T>
+where
+ T: ?Sized,
+{
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.inner
+ }
+}
+
+impl<T> PartialEq for LayoutAwareBox<T>
+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<T> Eq for LayoutAwareBox<T> where T: Eq + ?Sized {}
+
+impl<T> PartialOrd for LayoutAwareBox<T>
+where
+ T: PartialOrd + ?Sized,
+{
+ #[inline]
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
+ 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<T> Ord for LayoutAwareBox<T>
+where
+ T: Ord + ?Sized,
+{
+ #[inline]
+ fn cmp(&self, other: &Self) -> core::cmp::Ordering {
+ Ord::cmp(&**self, &**other)
+ }
+}
+
+impl<T> std::hash::Hash for LayoutAwareBox<T>
+where
+ T: std::hash::Hash + ?Sized,
+{
+ #[inline]
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ std::hash::Hash::hash(&**self, state)
+ }
+}
+
+impl<T> std::hash::Hasher for LayoutAwareBox<T>
+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<T> std::fmt::Display for LayoutAwareBox<T>
+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<T> std::fmt::Debug for LayoutAwareBox<T>
+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<T> std::fmt::Pointer for LayoutAwareBox<T>
+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<T> core::borrow::Borrow<T> for LayoutAwareBox<T>
+where
+ T: ?Sized,
+{
+ #[inline]
+ fn borrow(&self) -> &T {
+ self
+ }
+}
+
+impl<T> core::borrow::BorrowMut<T> for LayoutAwareBox<T>
+where
+ T: ?Sized,
+{
+ #[inline]
+ fn borrow_mut(&mut self) -> &mut T {
+ self
+ }
+}
+
+impl<T> core::convert::AsRef<T> for LayoutAwareBox<T>
+where
+ T: ?Sized,
+{
+ #[inline]
+ fn as_ref(&self) -> &T {
+ self
+ }
+}
+
+impl<T> core::convert::AsMut<T> for LayoutAwareBox<T>
+where
+ T: ?Sized,
+{
+ #[inline]
+ fn as_mut(&mut self) -> &mut T {
+ self
+ }
+}
+
+impl<E> core::error::Error for LayoutAwareBox<E>
+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<I> FromIterator<I> for LayoutAwareBox<[I]> {
+ #[inline]
+ fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
+ Self {
+ inner: ManuallyDrop::new(Box::from_iter(iter)),
+ type_size: TypeSize::Known,
+ }
+ }
+}
+
+impl<I> Iterator for LayoutAwareBox<I>
+where
+ I: Iterator + ?Sized,
+{
+ type Item = I::Item;
+
+ #[inline]
+ fn next(&mut self) -> Option<I::Item> {
+ (**self).next()
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (**self).size_hint()
+ }
+
+ #[inline]
+ fn nth(&mut self, n: usize) -> Option<I::Item> {
+ (**self).nth(n)
+ }
+}
+
+impl<I> DoubleEndedIterator for LayoutAwareBox<I>
+where
+ I: DoubleEndedIterator + ?Sized,
+{
+ #[inline]
+ fn next_back(&mut self) -> Option<I::Item> {
+ (**self).next_back()
+ }
+
+ #[inline]
+ fn nth_back(&mut self, n: usize) -> Option<I::Item> {
+ (**self).nth_back(n)
+ }
+}
+impl<I> ExactSizeIterator for LayoutAwareBox<I>
+where
+ I: ExactSizeIterator + ?Sized,
+{
+ #[inline]
+ fn len(&self) -> usize {
+ (**self).len()
+ }
+}
+
+impl<I> std::iter::FusedIterator for LayoutAwareBox<I> where I: std::iter::FusedIterator + ?Sized {}
+
+impl<R> std::io::Read for LayoutAwareBox<R>
+where
+ R: std::io::Read + ?Sized,
+{
+ #[inline]
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ (**self).read(buf)
+ }
+
+ #[inline]
+ fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
+ (**self).read_vectored(bufs)
+ }
+
+ #[inline]
+ fn read_to_end(&mut self, buf: &mut Vec<u8>) -> std::io::Result<usize> {
+ (**self).read_to_end(buf)
+ }
+
+ #[inline]
+ fn read_to_string(&mut self, buf: &mut String) -> std::io::Result<usize> {
+ (**self).read_to_string(buf)
+ }
+
+ #[inline]
+ fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
+ (**self).read_exact(buf)
+ }
+}
+
+impl<W> std::io::Write for LayoutAwareBox<W>
+where
+ W: std::io::Write + ?Sized,
+{
+ #[inline]
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ (**self).write(buf)
+ }
+
+ #[inline]
+ fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
+ (**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<S> std::io::Seek for LayoutAwareBox<S>
+where
+ S: std::io::Seek + ?Sized,
+{
+ #[inline]
+ fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
+ (**self).seek(pos)
+ }
+
+ #[inline]
+ fn rewind(&mut self) -> std::io::Result<()> {
+ (**self).rewind()
+ }
+
+ #[inline]
+ fn stream_position(&mut self) -> std::io::Result<u64> {
+ (**self).stream_position()
+ }
+
+ #[inline]
+ fn seek_relative(&mut self, offset: i64) -> std::io::Result<()> {
+ (**self).seek_relative(offset)
+ }
+}
+
+impl<B> std::io::BufRead for LayoutAwareBox<B>
+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<u8>) -> std::io::Result<usize> {
+ (**self).read_until(byte, buf)
+ }
+
+ #[inline]
+ fn skip_until(&mut self, byte: u8) -> std::io::Result<usize> {
+ (**self).skip_until(byte)
+ }
+
+ #[inline]
+ fn read_line(&mut self, buf: &mut String) -> std::io::Result<usize> {
+ (**self).read_line(buf)
+ }
+}
+
+// NOTE: The `From` conversion impls below are all "trivial" in the sense that
+// each `Box<T>` 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<T> From<T> for LayoutAwareBox<T> {
+ #[inline]
+ fn from(value: T) -> Self {
+ Self {
+ inner: ManuallyDrop::new(Box::from(value)),
+ type_size: TypeSize::Known,
+ }
+ }
+}
+
+impl<T> From<Box<T>> for LayoutAwareBox<T> {
+ #[inline]
+ fn from(value: Box<T>) -> Self {
+ Self {
+ inner: ManuallyDrop::new(value),
+ type_size: TypeSize::Known,
+ }
+ }
+}
+
+impl<T> From<Box<[T]>> for LayoutAwareBox<[T]> {
+ #[inline]
+ fn from(value: Box<[T]>) -> Self {
+ Self {
+ inner: ManuallyDrop::new(value),
+ type_size: TypeSize::Known,
+ }
+ }
+}
+
+impl<T> From<Vec<T>> for LayoutAwareBox<[T]> {
+ #[inline]
+ fn from(value: Vec<T>) -> Self {
+ Self {
+ inner: ManuallyDrop::new(Box::from(value)),
+ type_size: TypeSize::Known,
+ }
+ }
+}
+
+impl<T, const N: usize> 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<T> 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<T> 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<T> From<std::borrow::Cow<'_, [T]>> 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<usize> = 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::<usize>() * aware_boxed.len(),
+ align_of::<usize>()
+ )
+ .unwrap(),
+ "layout of slice from vector should have size = size_of::<T>() * len, align = align_of::<T>()"
+ );
+
+ drop(aware_boxed);
+
+ let boxed: Box<usize> = Box::new(42);
+ let aware_boxed = LayoutAwareBox::from(boxed);
+
+ assert_eq!(
+ aware_boxed,
+ LayoutAwareBox::new(42, align_of::<usize>()).expect("infallible"),
+ );
+
+ assert_eq!(
+ LayoutAwareBox::layout(&aware_boxed),
+ std::alloc::Layout::from_size_align(size_of::<usize>(), align_of::<usize>()).unwrap(),
+ "layout of value from Box should always have size = size_of::<T>(), align = align_of::<T>()"
+ );
+
+ 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::<usize>() * aware_boxed.len(),
+ align_of::<usize>()
+ )
+ .unwrap(),
+ "layout of slice from Box should always have size = size_of::<T>() * len, align = align_of::<T>()"
+ );
+
+ 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::<usize>() * aware_boxed.len(),
+ align_of::<usize>()
+ )
+ .unwrap(),
+ "layout of slice from [T; N] should always have size = size_of::<T>() * N, align = align_of::<T>()"
+ );
+
+ drop(aware_boxed);
+ }
+
+ #[test]
+ fn slice_fill_sized_nonempty() {
+ let aware_boxed =
+ LayoutAwareBox::slice_fill(7, align_of::<u64>(), 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::<u32>(),
+ concat!(
+ "for any [T] where T: Sized, align_of_val() is always align_of::<T>(),",
+ " because the compiler does not track alignment"
+ )
+ );
+
+ assert_eq!(
+ size_of_val(inner_ref),
+ size_of::<u32>() * 7,
+ "[T] where T: Sized with N elements should always have a size of size_of::<T>() * N"
+ );
+
+ assert_eq!(
+ LayoutAwareBox::layout(&aware_boxed),
+ std::alloc::Layout::from_size_align(
+ size_of::<u32>() * aware_boxed.len(),
+ align_of::<u64>()
+ )
+ .unwrap(),
+ "[T] where T: Sized with N elements should always have a layout where size = size_of::<T>() * 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::<u64>(), 8u32).expect("infallible"),
+ "equality sanity check failed"
+ );
+
+ let aware_boxed_other =
+ LayoutAwareBox::slice_fill_with(7, align_of::<u64>(), || 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::<usize>(),
+ "[T] where T: Sized with 0 elements should always have an alignment of align_of::<T>()"
+ );
+
+ assert_eq!(
+ LayoutAwareBox::layout(&aware_boxed),
+ std::alloc::Layout::from_size_align(0, align_of::<usize>()).unwrap(),
+ "[T] where T: Sized with 0 elements should always have a layout where size = 0, align = align_of::<T>()"
+ );
+
+ 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::<u8>() * len;
+ let align = align_of::<u8>();
+
+ 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::<MyDSTHeader>();
+ let data_len = alloc_size - header_size;
+
+ let layout = std::alloc::Layout::from_size_align(alloc_size, align_of::<MyDSTHeader>())
+ .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::<MyDSTHeader>();
+
+ let data_element_size = size_of::<MyPayloadElement>();
+ 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::<MyDSTHeader>())
+ .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::<MyDSTHeader>();
+
+ let data_element_size = size_of::<MyPayloadElement>();
+ 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::<MyDSTHeader>())
+ .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::<usize>::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
next prev parent reply other threads:[~2026-08-21 14:03 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-21 14:02 [PATCH proxmox{,-backup} v2 00/10] Fix Undefined Behavior in Tape Block Header Deallocation Max R. Carrara
2026-08-21 14:02 ` Max R. Carrara [this message]
2026-08-21 14:02 ` [PATCH proxmox v2 02/10] proxmox-alloc: document undefined behavior regarding custom allocs Max R. Carrara
2026-08-21 14:02 ` [PATCH proxmox-backup v2 03/10] tape: move tape block structs into separate file module Max R. Carrara
2026-08-21 14:02 ` [PATCH proxmox-backup v2 04/10] tape: rename `BlockHeader` and `BlockHeaderFlags` Max R. Carrara
2026-08-21 14:02 ` [PATCH proxmox-backup v2 05/10] tape: blocked_{reader,writer}: rename `buffer` to `tape_block` Max R. Carrara
2026-08-21 14:02 ` [PATCH proxmox-backup v2 06/10] tape: tape block: represent tape block header with its own struct Max R. Carrara
2026-08-21 14:02 ` [PATCH proxmox-backup v2 07/10] tape: tape block: make `payload` field private Max R. Carrara
2026-08-21 14:02 ` [PATCH proxmox-backup v2 08/10] tape: blocked_{reader,writer}: remove haphazard `unsafe` blocks Max R. Carrara
2026-08-21 14:02 ` [PATCH proxmox-backup v2 09/10] tape: tape block: fix undefined behavior on tape block deallocation Max R. Carrara
2026-08-21 14:02 ` [PATCH proxmox-backup v2 10/10] tape: sgutils2: fix undefined behavior in dealloc of buffer Max R. Carrara
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260821140238.615302-2-m.carrara@proxmox.com \
--to=m.carrara@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.