try_specialize/
type_eq.rs

1use core::any::TypeId;
2use core::marker::PhantomData;
3
4use crate::LifetimeFree;
5
6/// Returns `true` if the `T1` and `T2` types are equal.
7///
8/// This method requires only `T2` to implement [`LifetimeFree`] trait.
9///
10/// Library tests ensure that type comparisons are performed at compile time and
11/// are fully optimized with no runtime cost at `opt-level >= 1`. Note that the
12/// release profile uses `opt-level = 3` by default.
13///
14/// [`LifetimeFree`]: crate::LifetimeFree
15///
16/// # Examples
17///
18/// ```rust
19/// use try_specialize::type_eq;
20///
21/// assert!(type_eq::<(), ()>());
22/// assert!(!type_eq::<(), u8>());
23///
24/// assert!(type_eq::<u8, u8>());
25/// assert!(!type_eq::<u8, u32>());
26///
27/// assert!(type_eq::<[u8], [u8]>());
28/// assert!(type_eq::<[u8; 8], [u8; 8]>());
29/// assert!(!type_eq::<[u8; 8], [u8]>());
30/// assert!(!type_eq::<[u8], [u8; 8]>());
31/// assert!(!type_eq::<[u8; 8], [u8; 16]>());
32///
33/// assert!(type_eq::<str, str>());
34/// assert!(type_eq::<[u8], [u8]>());
35/// assert!(!type_eq::<str, [u8]>());
36/// assert!(!type_eq::<[u8], [u8; 4]>());
37///
38/// # #[cfg(feature = "alloc")]
39/// assert!(type_eq::<String, String>());
40/// ```
41#[inline]
42#[must_use]
43pub fn type_eq<T1, T2>() -> bool
44where
45    T1: ?Sized,
46    T2: ?Sized + LifetimeFree,
47{
48    type_eq_ignore_lifetimes::<T1, T2>()
49}
50
51/// Returns `true` if the `T1` and `T2` static types are equal.
52///
53/// This method requires both `T1` and `T2` to be `'static`.
54///
55/// Library tests ensure that type comparisons are performed at compile time and
56/// are fully optimized with no runtime cost at `opt-level >= 1`. Note that the
57/// release profile uses `opt-level = 3` by default.
58///
59/// # Examples
60///
61/// ```rust
62/// use try_specialize::static_type_eq;
63///
64/// fn static_type_eq_of_vals<T1: 'static, T2: 'static>(_: T1, _: T2) -> bool {
65///     static_type_eq::<T1, T2>()
66/// }
67///
68/// assert!(static_type_eq::<(), ()>());
69/// assert!(!static_type_eq::<(), u8>());
70///
71/// assert!(static_type_eq::<u8, u8>());
72/// assert!(!static_type_eq::<u8, u32>());
73///
74/// assert!(static_type_eq::<[u8], [u8]>());
75/// assert!(static_type_eq::<[u8; 8], [u8; 8]>());
76/// assert!(!static_type_eq::<[u8; 8], [u8]>());
77/// assert!(!static_type_eq::<[u8], [u8; 8]>());
78/// assert!(!static_type_eq::<[u8; 8], [u8; 16]>());
79///
80/// assert!(static_type_eq::<&'static str, &'static str>());
81/// assert!(static_type_eq_of_vals("foo", "bar"));
82///
83/// assert!(static_type_eq::<str, str>());
84/// assert!(static_type_eq::<[u8], [u8]>());
85/// assert!(!static_type_eq::<str, [u8]>());
86/// assert!(!static_type_eq::<[u8], [u8; 4]>());
87///
88/// # #[cfg(feature = "alloc")]
89/// assert!(static_type_eq::<String, String>());
90/// ```
91#[inline]
92#[must_use]
93pub fn static_type_eq<T1, T2>() -> bool
94where
95    T1: ?Sized + 'static,
96    T2: ?Sized + 'static,
97{
98    TypeId::of::<T1>() == TypeId::of::<T2>()
99}
100
101/// Returns `true` if the `Self` and `T` types are equal ignoring their
102/// lifetimes.
103///
104/// Note that all the lifetimes are erased and not accounted for.
105///
106/// Library tests ensure that type comparisons are performed at compile time and
107/// are fully optimized with no runtime cost at `opt-level >= 1`. Note that the
108/// release profile uses `opt-level = 3` by default.
109///
110/// # Examples
111///
112/// ```rust
113/// use core::hint::black_box;
114///
115/// use try_specialize::type_eq_ignore_lifetimes;
116///
117/// const STATIC_STR: &'static str = "foo";
118///
119/// assert!(type_eq_ignore_lifetimes::<(), ()>());
120/// assert!(!type_eq_ignore_lifetimes::<(), u8>());
121///
122/// assert!(type_eq_ignore_lifetimes::<u8, u8>());
123/// assert!(!type_eq_ignore_lifetimes::<u8, u32>());
124///
125/// assert!(type_eq_ignore_lifetimes::<[u8], [u8]>());
126/// assert!(type_eq_ignore_lifetimes::<[u8; 8], [u8; 8]>());
127/// assert!(!type_eq_ignore_lifetimes::<[u8; 8], [u8]>());
128/// assert!(!type_eq_ignore_lifetimes::<[u8], [u8; 8]>());
129/// assert!(!type_eq_ignore_lifetimes::<[u8; 8], [u8; 16]>());
130///
131/// assert!(type_eq_ignore_lifetimes::<str, str>());
132/// assert!(type_eq_ignore_lifetimes::<[u8], [u8]>());
133/// assert!(!type_eq_ignore_lifetimes::<str, [u8]>());
134/// assert!(!type_eq_ignore_lifetimes::<[u8], [u8; 4]>());
135///
136/// assert!(type_eq_ignore_lifetimes::<&'static str, &'static str>());
137/// assert!(type_eq_ignore_lifetimes_of_vals("foo", "bar"));
138/// assert!(type_eq_ignore_lifetimes_of_vals(
139///     STATIC_STR,
140///     format!("bar").as_str()
141/// ));
142///
143/// # #[cfg(feature = "alloc")]
144/// scoped_test();
145///
146/// # #[cfg(feature = "alloc")]
147/// fn scoped_test() {
148///     let local_str = format!("{}{}", black_box("foo"), black_box("bar"));
149///     let local_str = local_str.as_str(); // Non-static str.
150///     assert!(type_eq_ignore_lifetimes_of_vals(STATIC_STR, local_str));
151/// }
152///
153/// fn type_eq_ignore_lifetimes_of_vals<T1, T2>(_: T1, _: T2) -> bool {
154///     type_eq_ignore_lifetimes::<T1, T2>()
155/// }
156/// ```
157#[inline]
158#[must_use]
159pub fn type_eq_ignore_lifetimes<T1, T2>() -> bool
160where
161    T1: ?Sized,
162    T2: ?Sized,
163{
164    non_static_type_id::<T1>() == non_static_type_id::<T2>()
165}
166
167/// Returns the `TypeId` of the type this generic function has been instantiated
168/// with ignoring lifetimes.
169///
170/// Based on original implementation written by @dtolnay:
171/// <https://github.com/rust-lang/rust/issues/41875#issuecomment-317292888>
172///
173/// # Safety
174///
175/// This function doesn't validate type lifetimes. Lifetimes must be validated
176/// separately.
177#[inline]
178#[must_use]
179fn non_static_type_id<T>() -> TypeId
180where
181    T: ?Sized,
182{
183    trait NonStaticAny {
184        fn get_type_id(&self) -> TypeId
185        where
186            Self: 'static;
187    }
188
189    impl<T> NonStaticAny for PhantomData<T>
190    where
191        T: ?Sized,
192    {
193        #[inline]
194        fn get_type_id(&self) -> TypeId
195        where
196            Self: 'static,
197        {
198            TypeId::of::<T>()
199        }
200    }
201
202    // SAFETY: Types differs only by lifetimes. Lifetimes are the compile-time
203    // feature and are completely erased before the code generation stage.
204    // The transmuted type is only used to get its id and does not outlive the
205    // `get_type_id` function.
206    NonStaticAny::get_type_id(unsafe {
207        core::mem::transmute::<&dyn NonStaticAny, &(dyn NonStaticAny + 'static)>(&PhantomData::<T>)
208    })
209}
210
211#[cfg(test)]
212mod tests {
213    #[cfg(feature = "alloc")]
214    use alloc::string::String;
215    use core::cell::Ref;
216    use core::hint::black_box;
217
218    use crate::type_eq_ignore_lifetimes;
219
220    fn type_eq_ignore_lifetimes_of_vals<T1, T2>(_: T1, _: T2) -> bool {
221        type_eq_ignore_lifetimes::<T1, T2>()
222    }
223
224    #[cfg(feature = "alloc")]
225    #[inline(never)]
226    fn make_dummy_string() -> String {
227        #[expect(
228            clippy::arithmetic_side_effects,
229            reason = "false positive for string type"
230        )]
231        black_box(String::from("foo") + "bar")
232    }
233
234    #[test]
235    fn test_type_eq_ignore_lifetimes_with_local() {
236        test_type_eq_ignore_lifetimes_with_local_impl(&black_box(123));
237    }
238
239    #[expect(
240        clippy::trivially_copy_pass_by_ref,
241        reason = "ref is necessary for the test"
242    )]
243    fn test_type_eq_ignore_lifetimes_with_local_impl<'a>(local1: &'a u32) {
244        assert!(type_eq_ignore_lifetimes::<&'static str, &'a str>());
245        assert!(type_eq_ignore_lifetimes::<&'a str, &'static str>());
246        assert!(type_eq_ignore_lifetimes::<&'a str, &'a str>());
247        assert!(!type_eq_ignore_lifetimes::<&'a str, &'a u32>());
248
249        assert!(type_eq_ignore_lifetimes::<Ref<'static, u32>, Ref<'a, u32>>());
250        assert!(type_eq_ignore_lifetimes::<Ref<'a, u32>, Ref<'static, u32>>());
251        assert!(type_eq_ignore_lifetimes::<Ref<'a, u32>, Ref<'a, u32>>());
252        assert!(!type_eq_ignore_lifetimes::<Ref<'a, u32>, Ref<'a, u64>>());
253
254        let local2: &u32 = &black_box(234);
255        let local3: &i32 = &black_box(234);
256        assert!(type_eq_ignore_lifetimes_of_vals(local1, local2));
257        assert!(!type_eq_ignore_lifetimes_of_vals(local2, local3));
258        assert!(!type_eq_ignore_lifetimes_of_vals(local1, local3));
259    }
260
261    #[cfg(feature = "alloc")]
262    #[test]
263    fn test_type_eq_ignore_lifetimes_alloc() {
264        let string = make_dummy_string();
265        let non_static_str: &str = string.as_str();
266        assert!(type_eq_ignore_lifetimes_of_vals("foo", non_static_str));
267    }
268}