oversized_box/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! This crate provides a `Box`-like type that is allocated larger than
5//! necessary.
6//!
7//! This allows it to be reused for objects of different sizes without
8//! reallocating.
9
10// UNSAFETY: Manual memory management and pointer manipulation.
11#![expect(unsafe_code)]
12
13use std::marker::PhantomData;
14use std::mem::MaybeUninit;
15use std::ops::Deref;
16use std::ops::DerefMut;
17use std::pin::Pin;
18use std::ptr::NonNull;
19
20/// A `Box` for `T` with an allocation whose size and alignment is the same as
21/// `S`.
22pub struct OversizedBox<T: ?Sized, S> {
23    ptr: NonNull<T>,
24    phantom: PhantomData<*mut S>,
25}
26
27struct AssertFits<T, S>(PhantomData<(T, S)>);
28
29impl<T, S> AssertFits<T, S> {
30    const ASSERT: bool = {
31        if size_of::<T>() > size_of::<S>() {
32            panic!("T does not fit in S");
33        }
34        if align_of::<T>() > align_of::<S>() {
35            panic!("T has greater alignment than S does");
36        }
37        true
38    };
39}
40
41// SAFETY: passing through Send from T.
42unsafe impl<T: ?Sized + Send, S> Send for OversizedBox<T, S> {}
43// SAFETY: passing through Sync from T.
44unsafe impl<T: ?Sized + Sync, S> Sync for OversizedBox<T, S> {}
45impl<T: ?Sized, S> Unpin for OversizedBox<T, S> {}
46
47impl<T, S> OversizedBox<T, S> {
48    /// Allocates a new box and inserts `t`.
49    ///
50    /// ```rust
51    /// # use oversized_box::OversizedBox;
52    /// OversizedBox::<_, u64>::new(0u32);
53    /// ```
54    ///
55    /// Fails to compile if `T`'s size or alignment is larger than `S`'s.
56    ///
57    /// ```compile_fail
58    /// # use oversized_box::OversizedBox;
59    /// OversizedBox::<_, u32>::new(0u64);
60    /// ```
61    pub fn new(t: T) -> Self {
62        let _ = AssertFits::<T, S>::ASSERT;
63        let ptr = Box::into_raw(Box::new(MaybeUninit::<S>::uninit())).cast::<T>();
64        // SAFETY: `ptr` is a valid write target for `t`.
65        unsafe { ptr.write(t) };
66        Self {
67            ptr: NonNull::new(ptr).unwrap(),
68            phantom: PhantomData,
69        }
70    }
71
72    /// Allocates a new box, inserts `t`, and pins the box.
73    pub fn pin(t: T) -> Pin<Self> {
74        Self::into_pin(Self::new(t))
75    }
76}
77
78impl<T: ?Sized, S> OversizedBox<T, S> {
79    /// Drops the current contents of the box, then replaces them with `t`.
80    ///
81    /// Returns the new box, which may have a different type from the current
82    /// one.
83    ///
84    /// Panics if `T2`'s size or alignment is larger than `S`'s.
85    pub fn refill<T2>(this: Self, t: T2) -> OversizedBox<T2, S> {
86        let _ = AssertFits::<T2, S>::ASSERT;
87        // SAFETY: `ptr` uniquely owns the T we are dropping.
88        unsafe { std::ptr::drop_in_place(this.ptr.as_ptr()) };
89
90        let other = OversizedBox {
91            ptr: this.ptr.cast::<T2>(),
92            phantom: PhantomData,
93        };
94        std::mem::forget(this);
95
96        // SAFETY: `ptr` is now a valid target for writes of T2.
97        unsafe { other.ptr.as_ptr().write(t) };
98        other
99    }
100
101    /// Empties the box, dropping the contents but preserving the allocation.
102    pub fn empty(this: Self) -> OversizedBox<(), S> {
103        Self::refill(this, ())
104    }
105
106    /// Empties a pinned box, dropping the contents but preserving the
107    /// allocation.
108    pub fn empty_pinned(this: Pin<Self>) -> OversizedBox<(), S> {
109        // SAFETY: `empty` will just drop the current contents and not
110        // otherwise access it. The pinned object has no more references, so it
111        // does not violate any pin invariants to drop it and reuse the memory.
112        Self::empty(unsafe { Pin::into_inner_unchecked(this) })
113    }
114
115    /// Pins the box.
116    pub fn into_pin(this: Self) -> Pin<Self> {
117        // SAFETY: the underlying object is allocated on the heap and will not
118        // move until dropped.
119        unsafe { Pin::new_unchecked(this) }
120    }
121
122    /// Consumes `this` and returns the allocation plus the phantom data
123    /// specifying the allocation type.
124    ///
125    /// The phantom data is returned to make `coerce!` work.
126    pub fn into_raw(this: Self) -> (NonNull<T>, PhantomData<*mut S>) {
127        let Self { ptr, phantom } = this;
128        std::mem::forget(this);
129        (ptr, phantom)
130    }
131
132    /// Re-creates the box from the allocation plus phantom data specifying the
133    /// underlying allocation type.
134    ///
135    /// The phantom data is consumed to make `coerce!` work.
136    ///
137    /// # Safety
138    ///
139    /// `t` must have been returned from `into_raw`.
140    pub unsafe fn from_raw(t: NonNull<T>, s: PhantomData<*mut S>) -> Self {
141        Self { ptr: t, phantom: s }
142    }
143}
144
145/// Coerces an oversized box.
146///
147/// This is necessary because [`std::ops::CoerceUnsized`] is not stable.
148///
149/// # Example
150///
151/// ```rust
152/// # use oversized_box::OversizedBox;
153/// let x = OversizedBox::<_, u64>::new(5u32);
154/// let y: OversizedBox<dyn Send, u64> = oversized_box::coerce!(x);
155/// ```
156///
157/// You cannot use this to change the storage type. This will fail to build.
158///
159/// ```compile_fail
160/// # use oversized_box::OversizedBox;
161/// let x = OversizedBox::<_, u64>::new(5u32);
162/// let y: OversizedBox<dyn Send, u32> = oversized_box::coerce!(x);
163/// ```
164#[macro_export]
165macro_rules! coerce {
166    ($e:expr) => {
167        {
168            let e: OversizedBox<_, _> = $e;
169            let (t, s) = OversizedBox::into_raw(e);
170            // SAFETY: This will coerce `t` and `s` using normal coercion rules.
171            // Because `s` is a *mut S, it is invariant and will not coerce,
172            // which is what we want. But `t` is a NonNull<T>, so it will coerce
173            // to NonNull<T2> if T coerces to T2, which again is what we want.
174            unsafe { OversizedBox::from_raw(t, s) }
175        }
176    };
177}
178
179impl<T: ?Sized, S> Drop for OversizedBox<T, S> {
180    fn drop(&mut self) {
181        // SAFETY: `self.ptr` is owned and contains a T. But it's backed back a
182        // Box<MaybeUninit<S>>.
183        unsafe {
184            std::ptr::drop_in_place(self.ptr.as_ptr());
185            drop(Box::from_raw(self.ptr.as_ptr().cast::<MaybeUninit<S>>()));
186        }
187    }
188}
189
190impl<T: ?Sized, S> Deref for OversizedBox<T, S> {
191    type Target = T;
192
193    fn deref(&self) -> &Self::Target {
194        // SAFETY: ptr is valid for read.
195        unsafe { self.ptr.as_ref() }
196    }
197}
198
199impl<T: ?Sized, S> DerefMut for OversizedBox<T, S> {
200    fn deref_mut(&mut self) -> &mut Self::Target {
201        // SAFETY: ptr is valid for write.
202        unsafe { self.ptr.as_mut() }
203    }
204}
205
206impl<T: ?Sized, S> AsRef<T> for OversizedBox<T, S> {
207    fn as_ref(&self) -> &T {
208        self
209    }
210}
211
212impl<T: ?Sized, S> AsMut<T> for OversizedBox<T, S> {
213    fn as_mut(&mut self) -> &mut T {
214        self
215    }
216}
217
218impl<T: ?Sized, S> From<OversizedBox<T, S>> for Pin<OversizedBox<T, S>> {
219    fn from(this: OversizedBox<T, S>) -> Self {
220        OversizedBox::into_pin(this)
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use crate::OversizedBox;
227    use std::fmt::Display;
228
229    #[test]
230    fn basic_test() {
231        let x = OversizedBox::<_, [usize; 3]>::new(5u32);
232        println!("{}", x.as_ref());
233        let x = OversizedBox::refill(x, "now it's a string");
234        println!("{}", x.as_ref());
235        let x = OversizedBox::empty(x);
236        let x = OversizedBox::refill(x, "string again");
237        println!("{}", x.as_ref());
238        let x: OversizedBox<dyn Display, _> = coerce!(x);
239        println!("dyn {}", x.as_ref());
240    }
241}