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}