unty/lib.rs
1#![no_std]
2//! A crate that allows you to untype your types.
3//!
4//! This provides 2 functions:
5//!
6//! [`type_equal`] allows you to check if two types are the same.
7//!
8//! [`unty`] allows you to downcast a generic type into a concrete type.
9//!
10//! This is mostly useful for generic functions, e.g.
11//!
12//! ```
13//! # use unty::*;
14//! pub fn foo<S>(s: S) {
15//! if let Ok(a) = unsafe { unty::<S, u8>(s) } {
16//! println!("It is an u8 with value {a}");
17//! } else {
18//! println!("it is not an u8");
19//! }
20//! }
21//! foo(10u8); // will print "it is an u8"
22//! foo("test"); // will print "it is not an u8"
23//! ```
24//!
25//! Note that both of these functions may give false positives if both types have lifetimes. There currently is not a way to prevent this. See [`type_equal`] for more information.
26//!
27//! ```no_run
28//! # fn foo<'a>(input: &'a str) {
29//! # use unty::*;
30//! assert!(type_equal::<&'a str, &'static str>()); // these are not actually the same
31//! if let Ok(str) = unsafe { unty::<&'a str, &'static str>(input) } {
32//! // this will extend the &'a str lifetime to be &'static, which is not allowed.
33//! // the compiler may now light your PC on fire.
34//! }
35//! # }
36//! ```
37
38use core::{any::TypeId, marker::PhantomData, mem};
39
40/// Untypes your types. For documentation see the root of this crate.
41///
42/// # Safety
43///
44/// This should not be used with types with lifetimes.
45pub unsafe fn unty<Src, Target: 'static>(x: Src) -> Result<Target, Src> {
46 if type_equal::<Src, Target>() {
47 let x = mem::ManuallyDrop::new(x);
48 Ok(mem::transmute_copy::<Src, Target>(&x))
49 } else {
50 Err(x)
51 }
52}
53
54/// Checks to see if the two types are equal.
55///
56/// ```
57/// # use unty::type_equal;
58/// assert!(type_equal::<u8, u8>());
59/// assert!(!type_equal::<u8, u16>());
60///
61/// fn is_string<T>(_t: T) -> bool {
62/// type_equal::<T, String>()
63/// }
64///
65/// assert!(is_string(String::new()));
66/// assert!(!is_string("")); // `&'static str`, not `String`
67/// ```
68///
69/// Note that this may give false positives if both of the types have lifetimes. Currently it is not possible to determine the difference between `&'a str` and `&'b str`.
70///
71/// ```
72/// # use unty::type_equal;
73/// # fn foo<'a, 'b>(a: &'a str, b: &'b str) {
74/// assert!(type_equal::<&'a str, &'b str>()); // actual different types, this is not safe to transmute
75/// # }
76/// # foo("", "");
77/// ```
78pub fn type_equal<Src: ?Sized, Target: ?Sized>() -> bool {
79 non_static_type_id::<Src>() == non_static_type_id::<Target>()
80}
81
82// Code by dtolnay in a bincode issue:
83// https://github.com/bincode-org/bincode/issues/665#issue-1903241159
84fn non_static_type_id<T: ?Sized>() -> TypeId {
85 trait NonStaticAny {
86 fn get_type_id(&self) -> TypeId
87 where
88 Self: 'static;
89 }
90
91 impl<T: ?Sized> NonStaticAny for PhantomData<T> {
92 fn get_type_id(&self) -> TypeId
93 where
94 Self: 'static,
95 {
96 TypeId::of::<T>()
97 }
98 }
99
100 let phantom_data = PhantomData::<T>;
101 NonStaticAny::get_type_id(unsafe {
102 mem::transmute::<&dyn NonStaticAny, &(dyn NonStaticAny + 'static)>(&phantom_data)
103 })
104}
105
106#[test]
107fn test_double_drop() {
108 use core::sync::atomic::{AtomicUsize, Ordering};
109 #[derive(Debug)]
110 struct Ty;
111 static COUNTER: AtomicUsize = AtomicUsize::new(0);
112
113 impl Drop for Ty {
114 fn drop(&mut self) {
115 COUNTER.fetch_add(1, Ordering::Relaxed);
116 }
117 }
118
119 fn foo<T: core::fmt::Debug>(t: T) {
120 unsafe { unty::<T, Ty>(t) }.unwrap();
121 }
122
123 foo(Ty);
124 assert_eq!(COUNTER.load(Ordering::Relaxed), 1);
125}