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}