metatype/
lib.rs

1//! Helper methods to determine whether a type is `TraitObject`, `Slice` or
2//! `Concrete`, and work with them respectively.
3//!
4//! # Examples
5//!
6//! ```
7//! # use std::{any};
8//! # use metatype::*;
9//! assert_eq!(usize::METATYPE, MetaType::Concrete);
10//! assert_eq!(any::Any::METATYPE, MetaType::TraitObject);
11//! assert_eq!(<[u8]>::METATYPE, MetaType::Slice);
12//!
13//! let a: Box<usize> = Box::new(123);
14//! assert_eq!(Type::meta_type(&*a), MetaType::Concrete);
15//! let a: Box<dyn any::Any> = a;
16//! assert_eq!(Type::meta_type(&*a), MetaType::TraitObject);
17//!
18//! let a = [123,456];
19//! assert_eq!(Type::meta_type(&a), MetaType::Concrete);
20//! let a: &[i32] = &a;
21//! assert_eq!(Type::meta_type(a), MetaType::Slice);
22//!
23//! let a: Box<dyn any::Any> = Box::new(123);
24//! let meta: TraitObject = type_coerce(Type::meta(&*a));
25//! println!("vtable: {:?}", meta.vtable);
26//! ```
27//!
28//! # Note
29//!
30//! This currently requires Rust nightly for the `ptr_metadata`, `specialization`
31//! and `arbitrary_self_types_pointers` features.
32
33#![doc(html_root_url = "https://docs.rs/metatype/0.2.2")]
34#![feature(arbitrary_self_types_pointers)]
35#![feature(ptr_metadata)]
36#![feature(specialization)]
37#![warn(
38	missing_copy_implementations,
39	missing_debug_implementations,
40	missing_docs,
41	trivial_casts,
42	trivial_numeric_casts,
43	unused_import_braces,
44	unused_qualifications,
45	unused_results,
46	clippy::pedantic
47)] // from https://github.com/rust-unofficial/patterns/blob/master/anti_patterns/deny-warnings.md
48#![allow(
49	clippy::must_use_candidate,
50	clippy::not_unsafe_ptr_arg_deref,
51	clippy::use_self,
52	clippy::missing_panics_doc,
53	incomplete_features
54)]
55
56use std::{
57	any::{type_name, TypeId}, hash::{Hash, Hasher}, marker::PhantomData, mem::{align_of, align_of_val, forget, size_of, size_of_val, transmute_copy}, ptr::{slice_from_raw_parts_mut, NonNull}
58};
59
60/// Implemented on all types, it provides helper methods to determine whether a type is `TraitObject`, `Slice` or `Concrete`, and work with them respectively.
61pub trait Type {
62	/// Enum describing whether a type is `TraitObject`, `Slice` or `Concrete`.
63	const METATYPE: MetaType;
64	/// Type of metadata for type.
65	type Meta: 'static;
66	/// Helper method describing whether a type is `TraitObject`, `Slice` or `Concrete`.
67	fn meta_type(self: *const Self) -> MetaType {
68		Self::METATYPE
69	}
70	/// Retrieve [`TraitObject`], [`Slice`] or [`Concrete`] meta data respectively for a type
71	fn meta(self: *const Self) -> Self::Meta;
72	/// Retrieve pointer to the data
73	fn data(self: *const Self) -> *const ();
74	/// Retrieve mut pointer to the data
75	fn data_mut(self: *mut Self) -> *mut ();
76	/// Create a dangling non-null `*const Self` with the provided `Self::Meta`.
77	fn dangling(t: Self::Meta) -> NonNull<Self>;
78	/// Create a `*mut Self` with the provided `Self::Meta`.
79	fn fatten(thin: *mut (), t: Self::Meta) -> *mut Self;
80}
81/// Meta type of a type
82#[derive(Copy, Clone, PartialEq, Eq, Debug)]
83pub enum MetaType {
84	/// Trait object, thus unsized
85	TraitObject,
86	/// Slice, thus unsized
87	Slice,
88	/// Sized type
89	Concrete,
90}
91
92/// Meta data for a trait object
93#[derive(Copy, Clone, PartialEq, Eq, Debug)]
94pub struct TraitObject {
95	/// Address of vtable
96	pub vtable: &'static (),
97}
98/// Meta data for a slice
99#[derive(Copy, Clone, PartialEq, Eq, Debug)]
100pub struct Slice {
101	/// Number of elements in the slice
102	pub len: usize,
103}
104/// Meta data for a concrete, sized type
105#[derive(Copy, Clone, PartialEq, Eq, Debug)]
106pub struct Concrete;
107
108impl<T: ?Sized> Type for T {
109	#[doc(hidden)]
110	default const METATYPE: MetaType = MetaType::TraitObject;
111	#[doc(hidden)]
112	default type Meta = TraitObject;
113	#[inline]
114	default fn meta(self: *const Self) -> Self::Meta {
115		let ret = TraitObject {
116			vtable: unsafe { transmute_coerce(std::ptr::metadata(self)) },
117		};
118		type_coerce(ret)
119	}
120	#[inline]
121	default fn data(self: *const Self) -> *const () {
122		self.cast()
123	}
124	#[inline]
125	default fn data_mut(self: *mut Self) -> *mut () {
126		self.cast()
127	}
128	#[inline]
129	default fn dangling(t: Self::Meta) -> NonNull<Self> {
130		let t: TraitObject = type_coerce(t);
131		// align_of_val requires a reference: https://github.com/rust-lang/rfcs/issues/2017
132		// so to placate miri let's create one that's plausibly valid
133		let fake_thin = {
134			#[allow(dead_code)]
135			#[repr(align(64))]
136			struct Backing(u8);
137			static BACKING: Backing = Backing(0);
138			let backing: *const _ = &BACKING;
139			backing.cast::<()>().cast_mut()
140		};
141		let dangling_unaligned: NonNull<Self> =
142			NonNull::new(Self::fatten(fake_thin, type_coerce(t))).unwrap();
143		let dangling_unaligned: &Self = unsafe { dangling_unaligned.as_ref() };
144		let align = align_of_val(dangling_unaligned);
145		NonNull::new(Self::fatten(align as _, type_coerce(t))).unwrap()
146	}
147	#[inline]
148	default fn fatten(thin: *mut (), t: Self::Meta) -> *mut Self {
149		let t: TraitObject = type_coerce(t);
150		let vtable: *const () = t.vtable;
151		let vtable = vtable.cast_mut();
152		std::ptr::from_raw_parts_mut(thin, unsafe { transmute_coerce(vtable) })
153	}
154}
155#[doc(hidden)]
156impl<T: Sized> Type for T {
157	const METATYPE: MetaType = MetaType::Concrete;
158	type Meta = Concrete;
159	#[inline]
160	fn meta(self: *const Self) -> Self::Meta {
161		Concrete
162	}
163	#[inline]
164	fn data(self: *const Self) -> *const () {
165		self.cast()
166	}
167	#[inline]
168	fn data_mut(self: *mut Self) -> *mut () {
169		self.cast()
170	}
171	fn dangling(_t: Self::Meta) -> NonNull<Self> {
172		NonNull::dangling()
173	}
174	fn fatten(thin: *mut (), _t: Self::Meta) -> *mut Self {
175		thin.cast()
176	}
177}
178#[doc(hidden)]
179impl<T: Sized> Type for [T] {
180	const METATYPE: MetaType = MetaType::Slice;
181	type Meta = Slice;
182	#[allow(clippy::manual_slice_size_calculation)]
183	#[inline]
184	fn meta(self: *const Self) -> Self::Meta {
185		let self_ = unsafe { &*self }; // https://github.com/rust-lang/rfcs/issues/2017
186		assert_eq!(
187			(size_of_val(self_), align_of_val(self_)),
188			(size_of::<T>() * self_.len(), align_of::<T>())
189		);
190		Slice { len: self_.len() }
191	}
192	#[inline]
193	fn data(self: *const Self) -> *const () {
194		self.cast()
195	}
196	#[inline]
197	fn data_mut(self: *mut Self) -> *mut () {
198		self.cast()
199	}
200	fn dangling(t: Self::Meta) -> NonNull<Self> {
201		let slice = slice_from_raw_parts_mut(NonNull::<T>::dangling().as_ptr(), t.len);
202		unsafe { NonNull::new_unchecked(slice) }
203	}
204	fn fatten(thin: *mut (), t: Self::Meta) -> *mut Self {
205		slice_from_raw_parts_mut(thin.cast(), t.len)
206	}
207}
208#[doc(hidden)]
209impl Type for str {
210	const METATYPE: MetaType = MetaType::Slice;
211	type Meta = Slice;
212	#[inline]
213	fn meta(self: *const Self) -> Self::Meta {
214		let self_ = unsafe { &*self }; // https://github.com/rust-lang/rfcs/issues/2017
215		assert_eq!((size_of_val(self_), align_of_val(self_)), (self_.len(), 1));
216		Slice { len: self_.len() }
217	}
218	#[inline]
219	fn data(self: *const Self) -> *const () {
220		self.cast()
221	}
222	#[inline]
223	fn data_mut(self: *mut Self) -> *mut () {
224		self.cast()
225	}
226	fn dangling(t: Self::Meta) -> NonNull<Self> {
227		let bytes: *mut [u8] = <[u8]>::dangling(t).as_ptr();
228		unsafe { NonNull::new_unchecked(bytes as *mut Self) }
229	}
230	fn fatten(thin: *mut (), t: Self::Meta) -> *mut Self {
231		<[u8]>::fatten(thin, t) as *mut Self
232	}
233}
234
235unsafe fn transmute_coerce<A, B>(a: A) -> B {
236	assert_eq!(
237		(size_of::<A>(), align_of::<A>()),
238		(size_of::<B>(), align_of::<B>()),
239		"can't transmute_coerce {} to {} as sizes/alignments differ",
240		type_name::<A>(),
241		type_name::<B>()
242	);
243	let b = transmute_copy(&a);
244	forget(a);
245	b
246}
247
248/// Convert from one type parameter to another, where they are the same type.
249/// Panics with an explanatory message if the types differ.
250///
251/// In almost all circumstances this isn't needed, but it can be very useful in
252/// cases like [rust-lang/rust#50318](https://github.com/rust-lang/rust/issues/50318).
253pub fn type_coerce<A, B>(a: A) -> B {
254	try_type_coerce(a)
255		.unwrap_or_else(|| panic!("can't coerce {} to {}", type_name::<A>(), type_name::<B>()))
256}
257
258/// Convert from one type parameter to another, where they are the same type.
259/// Returns `None` if the types differ.
260///
261/// In almost all circumstances this isn't needed, but it can be very useful in
262/// cases like [rust-lang/rust#50318](https://github.com/rust-lang/rust/issues/50318).
263pub fn try_type_coerce<A, B>(a: A) -> Option<B> {
264	trait Eq<B> {
265		fn eq(self) -> Option<B>;
266	}
267
268	struct Foo<A, B>(A, PhantomData<fn(B)>);
269
270	impl<A, B> Eq<B> for Foo<A, B> {
271		default fn eq(self) -> Option<B> {
272			None
273		}
274	}
275	#[allow(clippy::mismatching_type_param_order)]
276	impl<A> Eq<A> for Foo<A, A> {
277		fn eq(self) -> Option<A> {
278			Some(self.0)
279		}
280	}
281
282	Foo::<A, B>(a, PhantomData).eq()
283}
284
285/// Gets an identifier which is globally unique to the specified type. This
286/// function will return the same value for a type regardless of whichever crate
287/// it is invoked in.
288pub fn type_id<T: ?Sized + 'static>() -> u64 {
289	let type_id = TypeId::of::<T>();
290	let mut hasher = std::collections::hash_map::DefaultHasher::new();
291	type_id.hash(&mut hasher);
292	hasher.finish()
293}
294
295#[cfg(test)]
296mod tests {
297	#![allow(clippy::cast_ptr_alignment, clippy::shadow_unrelated)]
298	use super::{type_coerce, MetaType, Slice, TraitObject, Type};
299	use std::{any, ptr::NonNull};
300
301	#[test]
302	fn abc() {
303		let a: Box<usize> = Box::new(123);
304		assert_eq!(Type::meta_type(&*a), MetaType::Concrete);
305		assert_eq!(Type::meta_type(&a), MetaType::Concrete);
306		let a: Box<dyn any::Any> = a;
307		assert_eq!(Type::meta_type(&*a), MetaType::TraitObject);
308		assert_eq!(Type::meta_type(&a), MetaType::Concrete);
309		let meta: TraitObject = type_coerce(Type::meta(&*a));
310		let dangling = <dyn any::Any as Type>::dangling(type_coerce(meta));
311		let _fat = <dyn any::Any as Type>::fatten(dangling.as_ptr().cast(), type_coerce(meta));
312		let mut x: usize = 0;
313		let x_ptr: *mut usize = &mut x;
314		let mut x_ptr: NonNull<dyn any::Any> = NonNull::new(<dyn any::Any as Type>::fatten(
315			x_ptr.cast(),
316			type_coerce(meta),
317		))
318		.unwrap();
319		let x_ref: &mut dyn any::Any = unsafe { x_ptr.as_mut() };
320		let x_ref: &mut usize = x_ref.downcast_mut().unwrap();
321		*x_ref = 123;
322		assert_eq!(x, 123);
323
324		let a: &[usize] = &[1, 2, 3];
325		assert_eq!(Type::meta_type(a), MetaType::Slice);
326		let dangling = <[String] as Type>::dangling(Slice { len: 100 });
327		let _fat = <[String] as Type>::fatten(dangling.as_ptr().cast(), Slice { len: 100 });
328
329		let a: Box<[usize]> = vec![1_usize, 2, 3].into_boxed_slice();
330		assert_eq!(Type::meta_type(&*a), MetaType::Slice);
331		assert_eq!(Type::meta_type(&a), MetaType::Concrete);
332
333		let a: &str = "abc";
334		assert_eq!(Type::meta_type(a), MetaType::Slice);
335		assert_eq!(Type::meta_type(&a), MetaType::Concrete);
336		let dangling = <str as Type>::dangling(Slice { len: 100 });
337		let _fat = <str as Type>::fatten(dangling.as_ptr().cast(), Slice { len: 100 });
338	}
339}