untyped_box/
std_conversions.rs

1use core::{alloc::Layout, mem::MaybeUninit, ptr::NonNull};
2
3use alloc::{boxed::Box, vec::Vec};
4
5use crate::{alloc_shim::Allocator, Allocation};
6
7/// Error when converting an [Allocation] to a [Box].
8#[derive(Debug, Clone)]
9#[non_exhaustive]
10pub enum BoxConversionError {
11    /// Indicates that the layout of the requested type does not match the layout of the allocation.
12    LayoutMismatch {
13        /// expected layout for the requested type
14        expected: Layout,
15        /// layout of the allocation
16        allocated: Layout,
17    },
18}
19
20impl BoxConversionError {
21    fn layout_mismatch(expected: Layout, allocated: Layout) -> Self {
22        Self::LayoutMismatch {
23            expected,
24            allocated,
25        }
26    }
27}
28
29/// Error when converting an [Allocation] to a [Vec].
30#[derive(Debug, Clone)]
31#[non_exhaustive]
32pub enum VecConversionError {
33    /// Indicates an alignment mismatch of the requested element type and the allocation.
34    AlignMismatch {
35        /// expected alignment for the requested type
36        expected: usize,
37        /// actual alignment of the allocation
38        allocated: usize,
39    },
40    /// Indicates that there is some slack capacity when trying to fit a whole multiple of
41    /// elements into the allocated memory to determine the vec's capacity.
42    SlackCapacity {
43        /// size of the requested element
44        element_size: usize,
45        /// allocated capacity in bytes
46        allocated: usize,
47    },
48    /// Tried to convert to a Vec of ZSTs. This is currently not supported due to ambiguity
49    /// of the requested capacity. Even if the allocation is zero-sized, we can fit an
50    /// arbitrary amount of elements as capacity.
51    ///
52    /// If you need this conversion to succeed, open an issue.
53    ZeroSizedElements,
54}
55
56impl VecConversionError {
57    fn align_mismatch(expected: usize, allocated: usize) -> Self {
58        Self::AlignMismatch {
59            expected,
60            allocated,
61        }
62    }
63    fn slack_capacity(element_size: usize, allocated: usize) -> Self {
64        Self::SlackCapacity {
65            element_size,
66            allocated,
67        }
68    }
69    fn zero_sized_elements() -> Self {
70        Self::ZeroSizedElements
71    }
72}
73
74// we can NOT write
75// impl<T, A: Allocator> TryFrom<crate::Allocation<A>> for Box<MaybeUninit<T>, A> {}
76// since   ^^^^^^^^^^^^ this is uncovered generic argument               here -^
77// Hence, we only support the conversion into the global allocator via trait.
78// THIS IS STUPID!
79impl<T> TryFrom<crate::Allocation> for Box<MaybeUninit<T>> {
80    type Error = BoxConversionError;
81    fn try_from(alloc: crate::Allocation) -> Result<Self, Self::Error> {
82        alloc.try_into_box::<T>()
83    }
84}
85
86fn check_box_layout<A: Allocator, T>(allocation: &Allocation<A>) -> Result<(), BoxConversionError> {
87    let expected = Layout::new::<T>();
88    let actual = allocation.layout();
89    if expected != actual {
90        return Err(BoxConversionError::layout_mismatch(expected, actual));
91    }
92    Ok(())
93}
94// TODO: conversion for unsized box/pointer metadata
95// TODO: conversion to ThinBox?
96
97fn check_vec_layout<A: Allocator, T>(
98    allocation: &Allocation<A>,
99) -> Result<usize, VecConversionError> {
100    let expected = Layout::new::<T>();
101    let actual = allocation.layout();
102    let element_align = expected.align();
103    let alloc_align = actual.align();
104    if element_align != alloc_align {
105        return Err(VecConversionError::align_mismatch(
106            element_align,
107            alloc_align,
108        ));
109    }
110    let element_size = expected.size();
111    let byte_capacity = actual.size();
112    #[allow(clippy::manual_is_multiple_of)] // would require MSRV of 1.87
113    if (element_size == 0 && byte_capacity != 0) || byte_capacity % element_size != 0 {
114        return Err(VecConversionError::slack_capacity(
115            element_size,
116            byte_capacity,
117        ));
118    }
119    if element_size == 0 {
120        // Can not determine a capacity.
121        // We can not make up ZSTs on the spot, so a capacity of 0 makes sense.
122        // TODO: let the user provide a capacity hint?
123        return Err(VecConversionError::zero_sized_elements());
124    }
125
126    let element_capacity = byte_capacity / element_size;
127    debug_assert!(byte_capacity == element_size * element_capacity);
128    Ok(element_capacity)
129}
130
131impl<T> TryFrom<crate::Allocation> for Vec<T> {
132    type Error = VecConversionError;
133
134    fn try_from(value: crate::Allocation) -> Result<Self, Self::Error> {
135        value.try_into_vec()
136    }
137}
138
139#[cfg(feature = "nightly-std-conversions")]
140mod alloc_allocator_api {
141    macro_rules! box_to_parts {
142        ($value:ident) => {
143            Box::into_raw_with_allocator($value)
144        };
145    }
146    macro_rules! vec_to_parts {
147        ($vec:ident) => {
148            Vec::into_raw_parts_with_alloc($vec)
149        };
150    }
151    macro_rules! box_from_parts {
152        ($ptr:expr, $alloc:expr) => {{
153            Box::from_raw_in($ptr, $alloc)
154        }};
155    }
156    macro_rules! vec_from_parts {
157        ($ptr:expr, $cap:expr, $alloc:expr) => {{
158            Vec::from_raw_parts_in($ptr, 0, $cap, $alloc)
159        }};
160    }
161    macro_rules! allocation_impl {
162        ( $( $imp:tt )* ) => {
163            type ABox<T, A> = alloc::boxed::Box<T, A>;
164            type AVec<T, A> = alloc::vec::Vec<T, A>;
165            impl<A: Allocator> crate::Allocation<A> {
166                $( $imp )*
167            }
168        };
169    }
170    macro_rules! from_box_impl {
171        ( $( #[doc = $doc:literal] )*
172          struct DocAnchor;
173          $( $imp:tt )*
174        ) => {
175            $( #[doc = $doc ] )*
176            impl<T: ?Sized, A: Allocator> From<Box<T, A>> for crate::Allocation<A> {
177                $( $imp )*
178            }
179        };
180    }
181    macro_rules! from_vec_impl {
182        ( $( #[doc = $doc:literal] )*
183          struct DocAnchor;
184          $( $imp:tt )*
185        ) => {
186            $( #[doc = $doc ] )*
187            impl<T, A: Allocator> From<Vec<T, A>> for crate::Allocation<A> {
188                $( $imp )*
189            }
190        };
191    }
192    pub(super) use allocation_impl;
193    pub(super) use box_from_parts;
194    pub(super) use box_to_parts;
195    pub(super) use from_box_impl;
196    pub(super) use from_vec_impl;
197    pub(super) use vec_from_parts;
198    pub(super) use vec_to_parts;
199}
200
201#[cfg(not(feature = "nightly-std-conversions"))]
202mod alloc_no_allocator_api {
203    macro_rules! box_to_parts {
204        ($value:ident) => {
205            (Box::into_raw($value), $crate::alloc_shim::Global)
206        };
207    }
208
209    macro_rules! vec_to_parts {
210        ($vec:ident) => {{
211            let mut $vec = core::mem::ManuallyDrop::new($vec);
212            // TODO: wait for feature(vec_into_raw_parts)
213            (
214                $vec.as_mut_ptr(),
215                $vec.len(),
216                $vec.capacity(),
217                $crate::alloc_shim::Global,
218            )
219        }};
220    }
221
222    macro_rules! box_from_parts {
223        ($ptr:expr, $alloc:expr) => {{
224            let _: $crate::alloc_shim::Global = $alloc;
225            alloc::boxed::Box::from_raw($ptr)
226        }};
227    }
228    macro_rules! vec_from_parts {
229        ($ptr:expr, $cap:expr, $alloc:expr) => {{
230            let _: $crate::alloc_shim::Global = $alloc;
231            alloc::vec::Vec::from_raw_parts($ptr, 0, $cap)
232        }};
233    }
234
235    macro_rules! allocation_impl {
236        ( $( $imp:tt )* ) => {
237            pub trait UseA<A> { type This: ?Sized; }
238            impl<A, T: ?Sized> UseA<A> for T { type This = Self; }
239            type ABox<T, A> = <alloc::boxed::Box<T> as UseA<A>>::This;
240            type AVec<T, A> = <alloc::vec::Vec<T> as UseA<A>>::This;
241
242            type A = $crate::alloc_shim::Global;
243            impl<> crate::Allocation<> {
244                $( $imp )*
245            }
246        };
247    }
248    macro_rules! from_box_impl {
249        ( $( #[doc = $doc:literal] )*
250          struct DocAnchor;
251          $( $imp:tt )*
252        ) => {
253            $( #[doc = $doc ] )*
254            impl<T: ?Sized> From<Box<T>> for crate::Allocation {
255                $( $imp )*
256            }
257        };
258    }
259    macro_rules! from_vec_impl {
260        ( $( #[doc = $doc:literal] )*
261          struct DocAnchor;
262          $( $imp:tt )*
263        ) => {
264            $( #[doc = $doc ] )*
265            impl<T> From<Vec<T>> for crate::Allocation {
266                $( $imp )*
267            }
268        };
269    }
270    pub(super) use allocation_impl;
271    pub(super) use box_from_parts;
272    pub(super) use box_to_parts;
273    pub(super) use from_box_impl;
274    pub(super) use from_vec_impl;
275    pub(super) use vec_from_parts;
276    pub(super) use vec_to_parts;
277}
278
279#[cfg(feature = "nightly-std-conversions")]
280use alloc_allocator_api as api_impl;
281#[cfg(not(feature = "nightly-std-conversions"))]
282use alloc_no_allocator_api as api_impl;
283
284api_impl::allocation_impl! {
285    /// Convert the allocation into a box.
286    ///
287    /// This fails if the allocated layout does not match the requested type. The value might not be initialized,
288    /// use [`Box::assume_init`] in case you have initialized the memory of this allocation correctly.
289    ///
290    /// See also the opposite conversion `Allocation as From<Box<_>>`.
291    // TODO: add intro-doc link to `<Allocation as From<Box<_>>>`
292    pub fn try_into_box<T>(self) -> Result<ABox<MaybeUninit<T>, A>, BoxConversionError> {
293        let () = check_box_layout::<_, T>(&self)?;
294        // Commit to the conversion
295        let (ptr, _, alloc) = self.into_parts_with_alloc();
296        let ptr = ptr.as_ptr().cast();
297        // SAFETY:
298        Ok(unsafe { api_impl::box_from_parts!(ptr, alloc) })
299    }
300
301    /// Convert the allocation into a [`Vec`].
302    ///
303    /// This fails if the allocated size is not a multiple of the requested element size, or if the element type is zero-sized.
304    /// For the latter case, the capacity of the `Vec` would be ambiguous.
305    ///
306    /// The length of the returned vec is always set to `0` and has to be resized manually with [`Vec::set_len`].
307    ///
308    /// See also the opposite conversion `Allocation as From<Vec<_>>`.
309    // TODO: add intro-doc link to `<Allocation as From<Vec<_>>>`
310    pub fn try_into_vec<T>(self) -> Result<AVec<T, A>, VecConversionError> {
311        let capacity = check_vec_layout::<_, T>(&self)?;
312        let (ptr, _, alloc) = self.into_parts_with_alloc();
313        let ptr = ptr.as_ptr().cast();
314        Ok(unsafe { api_impl::vec_from_parts!(ptr, capacity, alloc) })
315    }
316}
317
318// This has to appear side-by-side with allocation_impl because it relies on `A` and `ABox` to be defined
319
320api_impl::from_box_impl! {
321    /// The value in the box will not be dropped, as if passed to [`forget`](core::mem::forget).
322    /// Use the inverse (fallible) conversion to recover the value.
323    ///
324    /// ```
325    /// # use std::mem::MaybeUninit;
326    /// # use untyped_box::Allocation;
327    /// let boxed = Box::new(42);
328    /// let alloc: Allocation = boxed.into();
329    /// let boxed = alloc.try_into_box::<u32>().unwrap();
330    /// let boxed = unsafe { boxed.assume_init() };
331    /// assert_eq!(*boxed, 42);
332    /// ```
333    struct DocAnchor;
334
335    fn from(value: ABox<T, A>) -> Self {
336        let layout = Layout::for_value(&*value);
337        let (ptr, alloc) = api_impl::box_to_parts!(value);
338        let ptr = unsafe { NonNull::new_unchecked(ptr) };
339        unsafe { Self::from_parts_in(ptr.cast(), layout, alloc) }
340    }
341}
342
343// This has to appear side-by-side with allocation_impl because it relies on `A` and `ABox` to be defined
344
345api_impl::from_vec_impl! {
346    /// The values in the `Vec` will not be dropped, as if by a call to [`vec.set_len(0)`](Vec::set_len).
347    ///
348    /// ```
349    /// # use std::mem::MaybeUninit;
350    /// # use untyped_box::Allocation;
351    /// let values = vec![42];
352    /// let alloc: Allocation = values.into();
353    /// let mut values = alloc.try_into_vec::<u32>().unwrap();
354    /// unsafe { values.set_len(1) };
355    /// assert_eq!(values, [42]);
356    /// ```
357    struct DocAnchor;
358
359    fn from(value: AVec<T, A>) -> Self {
360        let mut value = value;
361        unsafe { value.set_len(0) };
362        let layout = Layout::for_value(value.spare_capacity_mut());
363        let (ptr, _, _, alloc) = api_impl::vec_to_parts!(value);
364        let ptr = unsafe { NonNull::new_unchecked(ptr) };
365        unsafe { Self::from_parts_in(ptr.cast(), layout, alloc) }
366    }
367}