[go: up one dir, main page]

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}