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}