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}