any_trait/
anyptr.rs

1/*
2 * This file is largely taken from `quinedot` response in:
3 * https://users.rust-lang.org/t/cast-from-concrete-to-any-and-subtraits/136086/4?u=lucafulchir
4 * and therefore the project's license and copyright don't apply here
5 */
6
7//! Type erasure for pointers
8//!
9//! Many, Many thanks to
10//! [**quinedot**](https://users.rust-lang.org/t/cast-from-concrete-to-any-and-subtraits/136086/4?u=lucafulchir)
11//! This is basically copied from his response
12
13use ::core::{
14    mem::{MaybeUninit, size_of, transmute_copy},
15    ptr::NonNull,
16};
17
18/// A pointer to a `Sized` type or to a `dyn Trait`.
19///
20/// modified from:
21/// [**quinedot**](https://users.rust-lang.org/t/cast-from-concrete-to-any-and-subtraits/136086/4?u=lucafulchir)
22///
23/// Rust has two main type of pointers:
24/// * thin pointer: same size as `usize`. we store those directly in `.data`
25/// * fat pointers: they have a `data` pointer and a `vtable` pointer.
26///
27/// Unfortunately in fat pointers there is no guerantee on which is first
28/// (data or vtable), so we need to check every time.\
29/// We store this information as a 1-byte offset in the `.meta` field.\
30/// Vtable pointers are guaranteed to be word-aligned.
31#[derive(Copy, Clone, Debug)]
32pub struct AnyPtr {
33    /// Pointer to the value.
34    ///
35    /// * If this is a thin pointer, `.meta` will be `None`
36    /// * If this is a fat pointer, this is the `data` part of the fat pointer
37    data: NonNull<()>,
38
39    /// Pointer to a `dyn Trait` vtable.
40    ///
41    /// If this is `None`, `AnyPtr` is a thin pointer.
42    /// If this is `Some()` then we have a fat pointer, and we have to
43    /// check the alignment of the vtable.
44    ///
45    /// * vtable %2 == 0 => the vtable comse first, the data ptr second
46    /// * vtable %2 == 1 => the data ptr comse first, the vtable second
47    meta: Option<NonNull<()>>,
48}
49
50impl AnyPtr {
51    /// Create a type-erased mutable pointer
52    ///
53    /// # Panics
54    /// If `ptr` is Null.
55    pub fn from<T: ?Sized>(ptr: *const T) -> Self {
56        const {
57            assert!(
58                size_of::<*const T>() == size_of::<NonNull<()>>()
59                    || size_of::<*const T>() == size_of::<[usize; 2]>()
60            )
61        }
62
63        return Self::from_mut::<T>(ptr as *mut T);
64    }
65
66    /// Create a type-erased pointer
67    ///
68    /// # Panics
69    /// If `ptr` is Null.
70    pub fn from_mut<T: ?Sized>(ptr: *mut T) -> Self {
71        const {
72            assert!(
73                size_of::<*const T>() == size_of::<NonNull<()>>()
74                    || size_of::<*const T>() == size_of::<[*mut (); 2]>()
75            )
76        }
77        if size_of::<*const T>() == size_of::<NonNull<()>>() {
78            // THIN pointer
79            return Self {
80                data: NonNull::new(ptr).unwrap().cast(),
81                meta: None,
82            };
83        }
84
85        // FAT pointer
86        let ptr = ptr as *const T;
87
88        // Detect the data pointer by changing its address.
89        // This will leave the metadata untouched.
90        let ptr2 = ptr.wrapping_byte_add(1);
91
92        // SAFETY: We've checked the size and are only comparing
93        // plain-old-data values.
94        let (before, after) = unsafe {
95            (
96                transmute_copy::<*const T, [*mut (); 2]>(&ptr),
97                transmute_copy::<*const T, [*mut (); 2]>(&ptr2),
98            )
99        };
100        let data = NonNull::new(ptr as *mut ()).unwrap();
101        let meta = if before[0] == after[0] {
102            // The data pointer should have been added to.
103            debug_assert_eq!(before[1] as usize, after[1] as usize - 1);
104            // Vtable pointers must be non-null and word-aligned.
105            debug_assert_ne!(0, before[0] as usize);
106            debug_assert_eq!(0, before[0] as usize % 2);
107
108            // It was the first pointer so we store it directly.
109            NonNull::new(before[0])
110        } else if before[1] == after[1] {
111            // The data pointer should have been added to.
112            debug_assert_eq!(before[0] as usize, after[0] as usize - 1);
113            // Vtable pointers must be non-null and word-aligned.
114            debug_assert_ne!(0, before[1] as usize);
115            debug_assert_eq!(0, before[1] as usize % 2);
116
117            // It was the second pointer so we flip the bottom bit.
118            NonNull::new(before[1].wrapping_byte_add(1))
119        } else {
120            unreachable!()
121        };
122
123        Self { data, meta }
124    }
125
126    /// Convert this pointer into a `NonNull<T>`.
127    ///
128    /// # Safety
129    ///
130    /// `self` **MUST** have been created by a call to either:
131    /// * `AnyPtr::of::<T>(ptr)`
132    /// * `AnyPtr::of_mut::<T>(ptr)`
133    pub unsafe fn to_ptr<T: ?Sized>(self) -> NonNull<T> {
134        const {
135            assert!(
136                size_of::<*const T>() == size_of::<NonNull<T>>()
137                    || size_of::<*const T>() == size_of::<[*mut (); 2]>()
138            )
139        }
140        let mut slot = MaybeUninit::<NonNull<T>>::uninit();
141        if let Some(meta) = self.meta {
142            assert_eq!(size_of::<*const T>(), size_of::<[*mut (); 2]>(),);
143            // We do the reverse convertion from the end of `of_unsized`.
144            let ptr = match meta.addr().get() % 2 {
145                0 => [meta.as_ptr(), self.data.as_ptr()],
146                1 => [self.data.as_ptr(), meta.as_ptr().wrapping_byte_sub(1)],
147                _ => unreachable!(),
148            };
149
150            // SAFETY: We only have `meta == None` when `T: Sized`
151            // and thus the size of `NonNull<[*mut (); 2]>` is the size
152            // of `NonNull<T>`.
153            let ptr =
154                unsafe { transmute_copy::<[*mut (); 2], NonNull<T>>(&ptr) };
155
156            slot.write(ptr);
157
158            // SAFETY: We just initialized the data.
159            // We have also reconstructed the pointer data with the same
160            // values and in the same order as the original created in
161            // `of_unsized::<T>`, thus preserving vtable invariants.
162            unsafe { slot.assume_init() }
163        } else {
164            assert_eq!(size_of::<*const T>(), size_of::<NonNull<T>>());
165            assert_eq!(size_of::<NonNull<T>>(), size_of::<NonNull<()>>());
166            // SAFETY: We only have `meta == None` when `T: Sized`
167            // and thus the size of `NonNull<()>` is the size of `NonNull<T>`.
168            let ptr = unsafe {
169                transmute_copy::<NonNull<()>, NonNull<T>>(&self.data)
170            };
171
172            slot.write(ptr);
173            // SAFETY: We just initialized the data.
174            unsafe { slot.assume_init() }
175        }
176    }
177}