vbox/lib.rs
1//! VBox is a type erased Box of trait object that stores the vtable pointer.
2//!
3//! `VBox` is just like a `Box<dyn Trait>` but erases type `Trait` so that to
4//! use it, there is no need to have `Trait` as one of its type parameters.
5//! Only the creator and the consumer needs to agree on the type
6//! parameters.
7//!
8//! Internally, it stores the trait object's data pointer in a `Box<dyn Any +
9//! Send>`, so that the `Drop::drop()` will be called when the wrapper is
10//! dropped. And it stores the vtable pointer in another `usize` to make sure it
11//! is `Send`.
12//!
13//! # Example
14//! ```
15//! # use std::fmt::{Debug, Display};
16//! # use vbox::{from_vbox, into_vbox, VBox};
17//! // Pack a u64 into a `Debug` trait object and erase the type `Debug`.
18//! let vbox: VBox = into_vbox!(dyn Debug, 10u64);
19//!
20//! // Unpack to different trait object will panic:
21//! // let _panic = from_vbox!(dyn Display, vbox);
22//!
23//! // Unpack the `Debug` trait object.
24//! let unpacked: Box<dyn Debug> = from_vbox!(dyn Debug, vbox);
25//!
26//! assert_eq!("10", format!("{:?}", unpacked));
27//! ```
28
29use std::any::Any;
30use std::any::TypeId;
31
32/// A type erased Box of trait object that stores the vtable pointer.
33///
34/// This is just like a `Box<dyn Trait>` but erases type `Trait` so that the
35/// channel for sending it does not need to have `Trait` as one of its type
36/// parameters. Only the sending end and the receiving end need to agree on the
37/// type parameters.
38///
39/// Internally, it stores the trait object's data pointer in a `Box<dyn Any>`,
40/// so that the `Drop::drop()` will be called when the wrapper is dropped.
41/// And it stores the vtable pointer in another `usize` to make sure it is
42/// `Send`.
43pub struct VBox {
44 /// The data pointer.
45 ///
46 /// Wrap it in a `Box` to make sure it is dropped when `VBox` is dropped.
47 data: Box<dyn Any + Send>,
48
49 /// The vtable pointer.
50 ///
51 /// Stored in `usize` to make sure it is `Send`.
52 vtable: usize,
53
54 /// Type id of `&dyn Trait`, for debugging.
55 type_id: TypeId,
56}
57
58impl VBox {
59 /// Create a new VBox. Do not use it directly. Use [`into_vbox!`] instead.
60 pub fn new(
61 data: Box<dyn Any + Send>,
62 vtable: usize,
63 type_id: TypeId,
64 ) -> Self {
65 VBox {
66 data,
67 vtable,
68 type_id,
69 }
70 }
71
72 /// Unpack the `VBox` and return the fields to rebuild the original trait
73 /// object. Do not use it directly. Use [`from_vbox!`] instead.
74 pub fn unpack(self) -> (Box<dyn Any + Send>, usize, TypeId) {
75 (self.data, self.vtable, self.type_id)
76 }
77}
78
79/// Create a [`VBox`] from a user defined type `T`.
80///
81/// The built `VBox` is another form of `Box<dyn Trait>`, where `T: Trait`.
82///
83/// See: [crate doc](crate)
84#[macro_export]
85macro_rules! into_vbox {
86 ($t: ty, $v: expr) => {{
87 let type_id = {
88 let trait_obj_ref: &$t = &$v;
89 ::std::any::Any::type_id(trait_obj_ref)
90 };
91
92 let vtable = {
93 let fat_ptr: *const $t = &$v;
94 let (_data, vtable): (*const (), *const ()) =
95 unsafe { ::std::mem::transmute(fat_ptr) };
96 vtable as usize
97 };
98
99 VBox::new(Box::new($v), vtable, type_id)
100 }};
101}
102
103/// Consume [`VBox`] and reconstruct the original trait object: `Box<dyn
104/// Trait>`.
105///
106/// It retrieve data pointer from `VBox.data` and the vtable pointer from
107/// `VBox.vtable`. Then it puts them together to reconstruct the fat pointer for
108/// the trait object.
109///
110/// See: [crate doc](crate)
111#[macro_export]
112macro_rules! from_vbox {
113 ($t: ty, $v: expr) => {{
114 let (data, vtable, type_id) = $v.unpack();
115
116 let any_fat_ptr: *const dyn ::core::any::Any = Box::into_raw(data);
117 let (data_ptr, _vtable): (*const (), *const ()) =
118 unsafe { ::std::mem::transmute(any_fat_ptr) };
119
120 let vtable_ptr = vtable as *const ();
121
122 let fat_ptr: *mut $t =
123 unsafe { ::std::mem::transmute((data_ptr, vtable_ptr)) };
124
125 let ret = unsafe { Box::from_raw(fat_ptr) };
126
127 {
128 let trait_obj_ref = &*ret;
129 debug_assert_eq!(
130 ::std::any::Any::type_id(trait_obj_ref),
131 type_id,
132 "expected type_id: {:?}, actual type_id: {:?}",
133 ::std::any::Any::type_id(trait_obj_ref),
134 type_id
135 );
136 }
137
138 ret
139 }};
140}