dyn_struct2/
lib.rs

1#![doc = include_str!("../README.md")]
2
3#![feature(ptr_metadata)]
4#![feature(decl_macro)]
5#![feature(coerce_unsized)]
6#![feature(unsize)]
7
8mod dyn_arg;
9
10pub use dyn_arg::*;
11
12#[cfg(feature = "derive")]
13pub use dyn_struct_derive2::DynStruct;
14
15use std::mem::{align_of, size_of};
16use std::ptr::{addr_of_mut, null_mut, Pointee};
17use transmute::transmute;
18
19#[repr(C)]
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub struct DynStruct<Header, Tail: ?Sized> {
22    pub header: Header,
23    pub tail: Tail,
24}
25
26impl<Header, Tail: ?Sized> DynStruct<Header, Tail> {
27    /// Allocate a new [DynStruct] on the heap.
28    #[inline]
29    pub fn new(header: Header, tail: DynArg<Tail>) -> Box<Self> {
30        let size = Self::size(&tail);
31        let align = Self::align(&tail);
32        // Metadata of struct = metadata of the unsized field
33        let metadata = tail.metadata();
34
35        // Allocate actual pointer
36        let thin_ptr = if size == 0 {
37            // Except we can't actually allocate 0 bytes, so we return null
38            null_mut() as *mut ()
39        } else {
40            unsafe {
41                // Actually allocate
42                let layout = std::alloc::Layout::from_size_align(size, align).unwrap();
43                let thin_ptr = std::alloc::alloc(layout) as *mut ();
44
45                // Check for allocation failure
46                if thin_ptr.is_null() {
47                    std::alloc::handle_alloc_error(layout)
48                }
49
50                thin_ptr
51            }
52        };
53
54        // Convert to fat pointer
55        let ptr: *mut Self = unsafe { transmute((thin_ptr, metadata)) };
56
57        unsafe {
58            // Get header and tail pointers
59            let header_ptr = addr_of_mut!((*ptr).header);
60            let tail_ptr = addr_of_mut!((*ptr).tail);
61
62            // Write header and tail
63            header_ptr.write(header);
64            tail.write_into(tail_ptr);
65        };
66
67
68        unsafe { Box::from_raw(ptr) }
69    }
70
71
72    /// SAFETY: `DynStruct<Header, Tail>` and `T` must have the same exact memory layout,
73    /// including fields, size, and alignment. They must also have the same pointer metadata.
74    ///
75    /// This function checks at compile-type the pointer metadata part. Use [more_unsafe_transmute]
76    /// if that check fails for some reason.
77    pub unsafe fn transmute<T: Pointee<Metadata = <Tail as Pointee>::Metadata> + ?Sized>(self: Box<Self>) -> Box<T> {
78        self.more_unsafe_transmute()
79    }
80
81    /// SAFETY: `DynStruct<Header, Tail>` and `T` must have the same exact memory layout,
82    /// including fields, size, and alignment. They must also have the same pointer metadata.
83    ///
84    /// This function *does not* check at compile-type the pointer metadata part.
85    /// Make sure the metadatas are the same or you will get some confusing runtime bugs.
86    pub unsafe fn more_unsafe_transmute<T: ?Sized>(self: Box<Self>) -> Box<T> {
87        let ptr = Box::into_raw(self);
88        let ptr: *mut T = transmute(ptr);
89        Box::from_raw(ptr)
90    }
91
92    #[inline]
93    fn align(tail: &DynArg<Tail>) -> usize {
94        usize::max(align_of::<Header>(), tail.align())
95    }
96
97    /// Returns the total size of the `DynStruct<Header, Tail>` structure, provided the length of the
98    /// tail.
99    #[inline]
100    fn size(tail: &DynArg<Tail>) -> usize {
101        let header = size_of::<Header>();
102        let tail_size = tail.size();
103        let tail_align = tail.align();
104
105        let padding = if header % tail_align == 0 {
106            0
107        } else {
108            tail_align - header % tail_align
109        };
110
111        header + padding + tail_size
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use std::fmt::Display;
118    use std::rc::Rc;
119    use crate::dyn_arg;
120    use super::*;
121
122    #[test]
123    fn sized_types() {
124        let tail = [1u64, 2, 3, 4];
125        let mixed = DynStruct::new((true, 32u16), dyn_arg!(tail));
126        assert_eq!(mixed.header, (true, 32u16));
127        assert_eq!(&mixed.tail, &[1, 2, 3, 4]);
128    }
129
130    #[test]
131    fn unsized_types() {
132        let tail = [1u64, 2, 3, 4];
133        let mixed = DynStruct::new((true, 32u16), dyn_arg!(tail) as DynArg<[u64]>);
134        assert_eq!(mixed.header, (true, 32u16));
135        assert_eq!(&mixed.tail, &[1, 2, 3, 4]);
136    }
137
138    #[test]
139    fn zero_sized_types() {
140        let tail = [(), ()];
141        let zero = DynStruct::new((), dyn_arg!(tail));
142        assert_eq!(zero.header, ());
143        assert_eq!(&zero.tail, &[(), ()]);
144    }
145
146    #[test]
147    fn non_copy_non_slice_types() {
148        let tail = Rc::new(42) as Rc<dyn Display>;
149        let tail_weak = Rc::downgrade(&tail);
150        let mixed = DynStruct::new(41, dyn_arg!(tail));
151        assert_eq!(mixed.header, 41);
152        assert_eq!(format!("{}", mixed.tail), "42");
153
154        // tail is still active
155        assert!(tail_weak.upgrade().is_some());
156
157        // Won't compile
158        // let mixed = DynStruct::new(41, dyn_arg!(tail));
159
160        drop(mixed); // drops tail
161        assert!(tail_weak.upgrade().is_none());
162    }
163
164    #[repr(C)]
165    struct SomeStruct {
166        foo: bool,
167        bar: usize,
168        baz: [u32]
169    }
170
171    #[test]
172    fn transmute() {
173        let tail = [1u32, 2, 3, 4];
174        let mixed = DynStruct::new((false, 50usize), dyn_arg!(tail) as DynArg<[u32]>);
175        assert_eq!(mixed.header, (false, 50usize));
176        assert_eq!(&mixed.tail, &[1u32, 2, 3, 4]);
177
178        let mixed = unsafe {  mixed.transmute::<SomeStruct>() };
179        assert_eq!(mixed.foo, false);
180        assert_eq!(mixed.bar, 50);
181        assert_eq!(&mixed.baz, &[1u32, 2, 3, 4]);
182
183        // This is a compiler error:
184        // let tail = [1u32, 2, 3, 4];
185        // let mixed2 = DynStruct::new((false, 50usize), dyn_arg!(tail)); // (no coerce unsized)
186        // let mixed2 = unsafe { mixed2.transmute::<SomeStruct>() }; // metadata is the wrong type
187    }
188}
189