static_box/lib.rs
1#![cfg_attr(not(test), no_std)]
2#![feature(ptr_metadata)]
3#![feature(unsize)]
4#![feature(const_pin)]
5// #![deny(missing_docs)]
6
7//! # Overview
8//!
9//! This crate allows saving DST objects in the provided buffer.
10//! In this way, it allows users to create global dynamic objects on a `no_std`
11//! environment without a global allocator.
12//!
13//! Imagine that you want to create a generic embedded logger which can be used for
14//! any board regardless of its hardware details. But you cannot just declare
15//! a static variable that implements some trait because the compiler doesn't know how
16//! much memory should be used to allocate it. In such cases, you have to use trait objects
17//! to erase the origin type. You might use the [`alloc::boxed::Box`](https://doc.rust-lang.org/stable/alloc/boxed/struct.Box.html)
18//! to do this thing, but it depends on the global allocator, which you also should provide,
19//! and there are a lot of caveats not to use heap on the embedded devices.
20//!
21//! Instead of using a global allocator, you can use this crate to store dynamic objects
22//! in the static memory.
23//!
24//! # Examples
25//!
26//! ```
27//! use static_box::Box;
28//!
29//! struct Uart1Rx {
30//! // Implementation details...
31//! }
32//!
33//! # trait SerialWrite {
34//! # fn write(&mut self, byte: u8);
35//! # fn write_str(&mut self, _s: &str) {}
36//! # }
37//! #
38//! impl SerialWrite for Uart1Rx {
39//! fn write(&mut self, _byte: u8) {
40//! // Implementation details
41//! }
42//! }
43//!
44//! let rx = Uart1Rx { /* ... */ };
45//! let mut mem = [0_u8; 32];
46//! let mut writer = Box::<dyn SerialWrite>::new(&mut mem, rx);
47//! writer.write_str("Hello world!");
48//! ```
49//!
50//! A more complex example demonstrating the usage of an external buffer.
51//! ```
52//! use core::fmt::Display;
53//! use static_box::Box;
54//!
55//! let mut mem = [0_u8; 64];
56//!
57//! let value = 42_u64;
58//! // Calculate the amount of memory needed to store this object.
59//! let total_len = {
60//! let layout = Box::<dyn Display>::layout_of_dyn(&value);
61//! let align_offset = mem.as_ptr().align_offset(layout.align());
62//! layout.size() + align_offset
63//! };
64//! let (head, _tail) = mem.split_at_mut(total_len);
65//!
66//! let val: Box<dyn Display> = Box::new(head, value);
67//! assert_eq!(val.to_string(), "42");
68//! ```
69//!
70//! # Limitations
71//!
72//! At the moment this crate can only store dynamic objects, but it's hard to imagine
73//! use cases where there is a need to store sized objects in this box.
74//!
75//! # Minimum Supported `rustc` Version
76//!
77//! This crate uses the following unstable features:
78//!
79//! - [`ptr_metadata`](https://doc.rust-lang.org/unstable-book/library-features/ptr-metadata.html)
80//! - [`unsize`](https://doc.rust-lang.org/unstable-book/library-features/unsize.html)
81//!
82//! In other words, the crate's supported **nightly** `rustc` version is `1.53.0`, but there
83//! is no guarantee that this code will work fine on the newest versions.
84//!
85//! # Implementation details
86//!
87//! **This crate uses unsafe code!**
88//!
89//! This crate inspired by the [`thin_box`](https://github.com/rust-lang/rust/blob/5ade3fe32c8a742504aaddcbe0d6e498f8eae11d/library/core/tests/ptr.rs#L561)
90//! example in the `rustc` tests repository.
91//!
92//! This implementation relies on that the type `V` can be coerced into the unsized `dyn T`,
93//! see the [`Unsize`](https://doc.rust-lang.org/core/marker/trait.Unsize.html) trait documentation.
94//! From the coerced `dyn T` it gets a pointer to metadata, which contains all the necessary
95//! information to manipulate the concrete type stored inside a trait object. After that it copies
96//! metadata and the origin value into the provided buffer.
97//!
98//! Thus, to get the pointer to the `dyn T`, you have to read the metadata given the memory alignment,
99//! use them to calculate the memory layout of the object, and only after that collect this
100//! all into the pointer. So, keep in mind that getting a reference to the stored `dyn T` could be too
101//! expensive in some cases.
102//!
103
104use core::{
105 alloc::Layout,
106 marker::{PhantomData, Unsize},
107 ops::{Deref, DerefMut},
108 ptr::{self, DynMetadata, NonNull, Pointee},
109};
110
111#[cfg(test)]
112mod tests;
113
114#[inline]
115fn meta_offset_layout<T, Value>(value: &Value) -> (DynMetadata<T>, Layout, usize)
116where
117 T: ?Sized + Pointee<Metadata = DynMetadata<T>>,
118 Value: Unsize<T> + ?Sized,
119{
120 // Get dynamic metadata for the given value.
121 let meta = ptr::metadata(value as &T);
122 // Compute memory layout to store the value + its metadata.
123 let meta_layout = Layout::for_value(&meta);
124 let value_layout = Layout::for_value(value);
125 let (layout, offset) = meta_layout.extend(value_layout).unwrap();
126 (meta, layout, offset)
127}
128
129/// A box that uses the provided memory to store dynamic objects.
130pub struct Box<'m, T>
131where
132 T: ?Sized + Pointee<Metadata = DynMetadata<T>>,
133{
134 align_offset: usize,
135 mem: &'m mut [u8],
136 phantom: PhantomData<T>,
137}
138
139impl<'m, T> Box<'m, T>
140where
141 T: ?Sized + Pointee<Metadata = DynMetadata<T>>,
142{
143 /// Places a `value` into the specified `mem` buffer. The user should provide enough memory
144 /// to store the value with its metadata considering alignment requirements.
145 ///
146 /// # Panics
147 ///
148 /// - If the provided buffer is insufficient to store the value.
149 pub fn new<Value>(mem: &'m mut [u8], value: Value) -> Self
150 where
151 Value: Unsize<T>,
152 {
153 let (meta, layout, offset) = meta_offset_layout(&value);
154 assert!(layout.size() > 0, "Unsupported value layot");
155
156 // Construct a box to move the specified memory into the necessary location.
157 // SAFETY: This code relies on the fact that this method will be inlined.
158 let mut new_box = Self {
159 align_offset: 0,
160 mem,
161 phantom: PhantomData,
162 };
163
164 let raw_ptr = new_box.mem.as_mut().as_mut_ptr();
165 // Compute the offset that needs to be applied to the pointer in order to make
166 // it aligned correctly.
167 new_box.align_offset = raw_ptr.align_offset(layout.align());
168
169 let total_len = new_box.align_offset + layout.size();
170 let buf_len = new_box.mem.as_ref().len();
171 // Check that the provided buffer has sufficient capacity to store the given value.
172 if total_len > buf_len {
173 // At the moment we cannot rely on the regular drop implementation because
174 // the box is in an inconsistent state.
175 core::mem::forget(new_box);
176 panic!(
177 "Not enough memory to store the specified value (got: {}, needed: {})",
178 buf_len, total_len,
179 );
180 }
181
182 unsafe {
183 let ptr = NonNull::new(raw_ptr.add(new_box.align_offset)).unwrap();
184 // Store dynamic metadata at the beginning of the given memory buffer.
185 ptr.cast::<DynMetadata<T>>().as_ptr().write(meta);
186 // Store the value in the remainder of the memory buffer.
187 ptr.cast::<u8>()
188 .as_ptr()
189 .add(offset)
190 .cast::<Value>()
191 .write(value);
192
193 new_box
194 }
195 }
196
197 /// Calculates layout describing a record that could be used
198 /// to allocate backing structure for `Value`.
199 #[inline]
200 pub fn layout_of_dyn<Value>(value: &Value) -> Layout
201 where
202 Value: Unsize<T> + ?Sized,
203 {
204 meta_offset_layout::<T, Value>(value).1
205 }
206
207 #[inline]
208 fn meta(&self) -> DynMetadata<T> {
209 unsafe { *self.mem.as_ref().as_ptr().add(self.align_offset).cast() }
210 }
211
212 #[inline]
213 fn layout_meta(&self) -> (Layout, usize, DynMetadata<T>) {
214 let meta = self.meta();
215 let (layout, offset) = Layout::for_value(&meta).extend(meta.layout()).unwrap();
216 (layout, offset, meta)
217 }
218
219 #[inline]
220 fn value_ptr(&self) -> *const T {
221 let (_, value_offset, meta) = self.layout_meta();
222 unsafe {
223 let ptr = self
224 .mem
225 .as_ref()
226 .as_ptr()
227 .add(self.align_offset)
228 .add(value_offset)
229 .cast::<()>();
230 ptr::from_raw_parts(ptr, meta)
231 }
232 }
233
234 #[inline]
235 fn value_mut_ptr(&mut self) -> *mut T {
236 let (_, value_offset, meta) = self.layout_meta();
237 unsafe {
238 let ptr = self
239 .mem
240 .as_mut()
241 .as_mut_ptr()
242 .add(self.align_offset)
243 .add(value_offset)
244 .cast::<()>();
245 ptr::from_raw_parts_mut(ptr, meta)
246 }
247 }
248}
249
250impl<'m, T> AsRef<T> for Box<'m, T>
251where
252 T: ?Sized + Pointee<Metadata = DynMetadata<T>>,
253{
254 #[inline]
255 fn as_ref(&self) -> &T {
256 unsafe { &*self.value_ptr() }
257 }
258}
259
260impl<'m, T> AsMut<T> for Box<'m, T>
261where
262 T: ?Sized + Pointee<Metadata = DynMetadata<T>>,
263{
264 #[inline]
265 fn as_mut(&mut self) -> &mut T {
266 unsafe { &mut *self.value_mut_ptr() }
267 }
268}
269
270impl<'m, T> Deref for Box<'m, T>
271where
272 T: ?Sized + Pointee<Metadata = DynMetadata<T>>,
273{
274 type Target = T;
275
276 #[inline]
277 fn deref(&self) -> &T {
278 self.as_ref()
279 }
280}
281
282impl<'m, T> DerefMut for Box<'m, T>
283where
284 T: ?Sized + Pointee<Metadata = DynMetadata<T>>,
285{
286 #[inline]
287 fn deref_mut(&mut self) -> &mut T {
288 self.as_mut()
289 }
290}
291
292impl<'m, T> Drop for Box<'m, T>
293where
294 T: ?Sized + Pointee<Metadata = DynMetadata<T>>,
295{
296 #[inline]
297 fn drop(&mut self) {
298 unsafe {
299 ptr::drop_in_place::<T>(&mut **self);
300 }
301 }
302}