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}