try_specialize/
try_specialize.rs

1use crate::{LifetimeFree, Specialization};
2
3/// A trait for specializing one type to another at runtime.
4///
5/// This trait uses [`Specialization`] helper struct to perform all
6/// conversions. You can use [`Specialization`] directly if you need to perform
7/// more complex specialization cases or to cache the specializable ability.
8///
9/// Library tests ensure that the specializations are performed at compile time
10/// and are fully optimized with no runtime cost at `opt-level >= 1`. Note that
11/// the release profile uses `opt-level = 3` by default.
12///
13/// Methods cheat sheet:
14/// | Bounds\Operation | specialize `T1` to `T2` | specialize `&T1` to `&T2` | specialize `&mut T1` to `&mut T2` |
15/// |:--:|:--:|:--:|:--:|
16/// | `T1: 'static` <br /> `+ LifetimeFree` | [`try_specialize`] | [`try_specialize_ref`] | [`try_specialize_mut`] |
17/// | `T2: 'static` <br /> `+ LifetimeFree` | [`try_specialize_from`] | [`try_specialize_from_ref`] | [`try_specialize_from_mut`] |
18/// | `T1: 'static,` <br /> `T2: 'static,` | [`try_specialize_static`] | [`try_specialize_ref_static`] | [`try_specialize_mut_static`] |
19/// | unconstrained <br /> underlying type <br /> maybe impls <br /> `LifetimeFree` | [`..._if_lifetime_free_weak`] | [`..._ref_if_lifetime_free_weak`] | [`..._mut_if_lifetime_free_weak`] |
20/// | unconstrained <br /> `unsafe` without <br /> lifetimes check | [`..._ignore_lifetimes`] | [`..._ref_ignore_lifetimes`] | [`..._mut_ignore_lifetimes`] |
21///
22/// [`try_specialize`]: TrySpecialize::try_specialize
23/// [`try_specialize_ref`]: TrySpecialize::try_specialize_ref
24/// [`try_specialize_mut`]: TrySpecialize::try_specialize_mut
25/// [`try_specialize_from`]: TrySpecialize::try_specialize_from
26/// [`try_specialize_from_ref`]: TrySpecialize::try_specialize_from_ref
27/// [`try_specialize_from_mut`]: TrySpecialize::try_specialize_from_mut
28/// [`try_specialize_static`]: TrySpecialize::try_specialize_static
29/// [`try_specialize_ref_static`]: TrySpecialize::try_specialize_ref_static
30/// [`try_specialize_mut_static`]: TrySpecialize::try_specialize_mut_static
31/// [`..._ignore_lifetimes`]: TrySpecialize::try_specialize_ignore_lifetimes
32/// [`..._ref_ignore_lifetimes`]: TrySpecialize::try_specialize_ref_ignore_lifetimes
33/// [`..._mut_ignore_lifetimes`]: TrySpecialize::try_specialize_mut_ignore_lifetimes
34/// [`..._if_lifetime_free_weak`]: crate::unreliable::TrySpecializeWeak::try_specialize_if_lifetime_free_weak
35/// [`..._ref_if_lifetime_free_weak`]: crate::unreliable::TrySpecializeWeak::try_specialize_ref_if_lifetime_free_weak
36/// [`..._mut_if_lifetime_free_weak`]: crate::unreliable::TrySpecializeWeak::try_specialize_mut_if_lifetime_free_weak
37pub trait TrySpecialize {
38    /// Attempts to specialize `Self` as `T` for types without lifetimes.
39    ///
40    /// Returns `Self` as `T` wrapped in `Ok` if `Self` and `T` types are
41    /// identical. Otherwise, it returns `Self` wrapped in `Err`.
42    ///
43    /// Note that this method requires target type to implement
44    /// [`LifetimeFree`]. Use [`TrySpecialize::try_specialize_from`] if your
45    /// target type doesn't have [`LifetimeFree`] bound but source type does.
46    /// Use [`TrySpecialize::try_specialize_static`] if both source and target
47    /// type have `'static` bounds.
48    ///
49    /// The [`LifetimeFree`] trait is **not** automatically derived for all
50    /// lifetime-free types. The library only implements it for standard library
51    /// types that do not have any lifetime parameters.
52    ///
53    /// [`LifetimeFree`]: crate::LifetimeFree
54    ///
55    /// # Examples
56    ///
57    /// Simple partial reimplementation of stdlib `ToString`:
58    /// ```rust
59    /// # #[cfg(feature = "std")] {
60    /// use core::fmt::Display;
61    ///
62    /// use try_specialize::TrySpecialize;
63    ///
64    /// fn to_string<T>(value: T) -> String
65    /// where
66    ///     T: Display,
67    /// {
68    ///     match value.try_specialize() {
69    ///         Ok(string) => string,
70    ///         Err(value) => format!("{value}"),
71    ///     }
72    /// }
73    ///
74    /// assert_eq!(to_string("abc".to_owned()), "abc".to_owned()); // Specialized.
75    /// assert_eq!(to_string(123), String::from("123")); // Default.
76    /// # }
77    /// ```
78    ///
79    /// Note that many standard library types and traits, including `ToString`,
80    /// already use specialization for optimization purposes using
81    /// `min_specialization` nightly feature.
82    #[expect(clippy::missing_errors_doc, reason = "already described")]
83    #[inline]
84    fn try_specialize<T>(self) -> Result<T, Self>
85    where
86        Self: Sized,
87        T: LifetimeFree,
88    {
89        if let Some(spec) = Specialization::try_new() {
90            Ok(spec.specialize(self))
91        } else {
92            Err(self)
93        }
94    }
95
96    /// Attempts to specialize `T` as `Self` for types without lifetimes.
97    ///
98    /// Returns `T` as `Self` wrapped in `Ok` if `Self` and `T` types are
99    /// identical. Otherwise, it returns `T` wrapped in `Err`.
100    ///
101    /// Note that this method requires source type to implement
102    /// [`LifetimeFree`]. Use [`TrySpecialize::try_specialize_from`] if your
103    /// source type doesn't have [`LifetimeFree`] bound but target type does.
104    /// Use [`TrySpecialize::try_specialize_static`] if both target and source
105    /// type have `'static` bounds.
106    /// The [`LifetimeFree`] trait is **not** automatically derived for all
107    /// lifetime-free types. The library only implements it for standard library
108    /// types that do not have any lifetime parameters.
109    ///
110    /// [`LifetimeFree`]: crate::LifetimeFree
111    ///
112    /// # Examples
113    ///
114    /// Generate a placeholder with non-default value for some types:
115    /// ```rust
116    /// # #[cfg(feature = "alloc")] {
117    /// use try_specialize::{Specialization, TrySpecialize};
118    ///
119    /// fn placeholder<T>() -> T
120    /// where
121    ///     T: Default,
122    /// {
123    ///     None.or_else(|| T::try_specialize_from(12_u8).ok())
124    ///         .or_else(|| T::try_specialize_from(234_u16).ok())
125    ///         .or_else(|| T::try_specialize_from(3456_u32).ok())
126    ///         .or_else(|| T::try_specialize_from(45678_u64).ok())
127    ///         .or_else(|| T::try_specialize_from(567_890_u128).ok())
128    ///         .or_else(|| T::try_specialize_from(123_456_789_usize).ok())
129    ///         .or_else(|| T::try_specialize_from(123.456_f32).ok())
130    ///         .or_else(|| T::try_specialize_from(123.456_f64).ok())
131    ///         .or_else(|| {
132    ///             // SAFETY: For any `'a` It is safe to specialize `&'static str`
133    ///             // as `&'a str`.
134    ///             unsafe { "dummy string".try_specialize_ignore_lifetimes() }.ok()
135    ///         })
136    ///         .or_else(|| {
137    ///             let spec/*: Specialization<T, String>*/ = Specialization::try_new()?;
138    ///             Some(spec.rev().specialize(String::from("foobar")))
139    ///         })
140    ///         .or_else(|| {
141    ///             let spec/*: Specialization<T, Box<str>>*/ = Specialization::try_new()?;
142    ///             Some(spec.rev().specialize(String::from("bazz").into_boxed_str()))
143    ///         })
144    ///         .unwrap_or_default()
145    /// }
146    ///
147    /// assert_eq!(placeholder::<(u8, u8)>(), (0, 0));
148    /// assert_eq!(placeholder::<u32>(), 3456);
149    /// assert_eq!(placeholder::<f64>(), 123.456_f64);
150    /// assert_eq!(placeholder::<u128>(), 567_890);
151    /// assert_eq!(placeholder::<i128>(), 0);
152    /// assert_eq!(placeholder::<&'static str>(), "dummy string");
153    /// assert_eq!(placeholder::<String>(), String::from("foobar"));
154    /// assert_eq!(placeholder::<Box<str>>(), Box::from("bazz"));
155    /// # }
156    /// ```
157    #[expect(clippy::missing_errors_doc, reason = "already described")]
158    #[inline]
159    fn try_specialize_from<T>(other: T) -> Result<Self, T>
160    where
161        Self: Sized,
162        T: LifetimeFree,
163    {
164        if let Some(spec) = Specialization::try_new() {
165            Ok(spec.rev().specialize(other))
166        } else {
167            Err(other)
168        }
169    }
170
171    /// Attempts to specialize `Self` as `T` for static types.
172    ///
173    /// Returns `Self` as `T` wrapped in `Ok` if `Self` and `T` types are
174    /// identical. Otherwise, it returns `Self` wrapped in `Err`.
175    ///
176    /// Note that this method requires both types to have `'static` lifetime,
177    /// but don't require any type to implement [`LifetimeFree`]. If one of your
178    /// types does not have a 'static bounds but the other type implements
179    /// [`LifetimeFree`] use [`TrySpecialize::try_specialize`] or
180    /// [`TrySpecialize::try_specialize_from`] instead.
181    ///
182    /// # Note
183    ///
184    /// This function requires both the source and destination types to
185    /// implement `'static`. Although most `'static` types in Rust can be
186    /// subtypes to a non-`'static` alternatives this is not always the case.
187    /// For example `fn(&'static str)` and `fn(&'a str)` have the same `TypeId`
188    /// however you can't subtype the first to the second, because, unlike
189    /// anything else in the language, functions are contravariant over their
190    /// arguments. See <https://doc.rust-lang.org/reference/subtyping.html#variance>
191    /// and <https://doc.rust-lang.org/nomicon/subtyping.html#variance> for
192    /// more details.
193    ///
194    /// # Examples
195    ///
196    /// Function with specialized implementation for
197    /// [`std::collections::HashMap`] and `hashbrown::HashMap`:
198    /// ```rust
199    /// use try_specialize::TrySpecialize;
200    ///
201    /// fn process_static<T>(value: T)
202    /// where
203    ///     T: 'static,
204    /// {
205    ///     match value.try_specialize_static::<std::collections::HashMap<u32, char>>() {
206    ///         Ok(hash_map @ std::collections::HashMap { .. }) => {
207    ///             drop(hash_map);
208    ///             // specialized impl for `std::collections::HashMap`
209    ///         }
210    ///         Err(value) => {
211    ///             match value.try_specialize_static::<hashbrown::HashMap<u32, char>>() {
212    ///                 Ok(hash_map @ hashbrown::HashMap { .. }) => {
213    ///                     drop(hash_map);
214    ///                     // specialized impl for `hashbrown::HashMap`
215    ///                 }
216    ///                 Err(default) => {
217    ///                     drop(default);
218    ///                     // default impl ...
219    ///                 }
220    ///             }
221    ///         }
222    ///     }
223    /// }
224    /// # let input = [(123_u32, 'a'), (234_u32, 'b')];
225    /// # process_static(input.into_iter().collect::<std::collections::HashMap::<_, _>>());
226    /// # process_static(input.into_iter().collect::<hashbrown::HashMap::<_, _>>());
227    /// # process_static(input.into_iter().collect::<Vec::<_>>());
228    /// ```
229    #[expect(clippy::missing_errors_doc, reason = "already described")]
230    #[inline]
231    fn try_specialize_static<T>(self) -> Result<T, Self>
232    where
233        Self: 'static + Sized,
234        T: 'static,
235    {
236        if let Some(spec) = Specialization::try_new_static() {
237            Ok(spec.specialize(self))
238        } else {
239            Err(self)
240        }
241    }
242
243    /// Attempts to specialize `&Self` as `&T` for types without lifetimes.
244    ///
245    /// Note that this method requires target type to implement
246    /// [`LifetimeFree`]. Use [`TrySpecialize::try_specialize_from_ref`] if your
247    /// target type doesn't implement [`LifetimeFree`] but source type does.
248    ///
249    /// The [`LifetimeFree`] trait is **not** automatically derived for all
250    /// lifetime-free types. The library only implements it for standard library
251    /// types that do not have any lifetime parameters.
252    ///
253    /// [`LifetimeFree`]: crate::LifetimeFree
254    ///
255    /// # Examples
256    ///
257    /// Stringifiable type concatenation, that don't allocate memory if one of
258    /// the arguments is empty `&str`:
259    /// ```rust
260    /// use core::fmt::Display;
261    /// use std::borrow::Cow;
262    /// use std::format;
263    ///
264    /// use try_specialize::TrySpecialize;
265    ///
266    /// fn concat<'a, T1, T2>(first: &'a T1, second: &'a T2) -> Cow<'a, str>
267    /// where
268    ///     T1: ?Sized + Display,
269    ///     T2: ?Sized + Display,
270    /// {
271    ///     match (
272    ///         first.try_specialize_ref(),
273    ///         second.try_specialize_ref(),
274    ///     ) {
275    ///         (Some(first), Some("")) => Cow::Borrowed(first),
276    ///         (Some(""), Some(second)) => Cow::Borrowed(second),
277    ///         (_, _) => Cow::Owned(format!("{first}{second}")),
278    ///     }
279    /// }
280    ///
281    /// assert!(matches!(concat("foo", "bar"), Cow::Owned(v) if v == "foobar"));
282    /// assert!(matches!(concat("foo", ""), Cow::Borrowed("foo")));
283    /// assert!(matches!(concat("", "bar"), Cow::Borrowed("bar")));
284    /// let foo = String::from("foo");
285    /// let bar = String::from("bar");
286    /// assert!(matches!(concat(&foo, &bar), Cow::Owned(v) if v == "foobar"));
287    /// assert!(matches!(concat("foo", &456), Cow::Owned(v) if v == "foo456"));
288    /// assert!(matches!(concat(&123, &456), Cow::Owned(v) if v == "123456"));
289    /// ```
290    #[inline]
291    fn try_specialize_ref<T>(&self) -> Option<&T>
292    where
293        T: ?Sized + LifetimeFree,
294    {
295        Specialization::try_new().map(|spec| spec.specialize_ref(self))
296    }
297
298    /// Attempts to specialize `&T` as `&Self` for types without lifetimes.
299    ///
300    /// Note that this method requires source type to implement
301    /// [`LifetimeFree`]. Use [`TrySpecialize::try_specialize_ref`] if your
302    /// source type doesn't implement [`LifetimeFree`] but target type does.
303    /// The [`LifetimeFree`] trait is **not** automatically derived for all
304    /// lifetime-free types. The library only implements it for standard library
305    /// types that do not have any lifetime parameters.
306    ///
307    /// [`LifetimeFree`]: crate::LifetimeFree
308    #[inline]
309    fn try_specialize_from_ref<T>(other: &T) -> Option<&Self>
310    where
311        T: ?Sized + LifetimeFree,
312    {
313        Specialization::try_new().map(|spec| spec.rev().specialize_ref(other))
314    }
315
316    /// Attempts to specialize `&Self` as `&T` for static types.
317    ///
318    /// Note that this method requires both types to have `'static` lifetime,
319    /// but don't require any type to implement [`LifetimeFree`].
320    #[inline]
321    fn try_specialize_ref_static<T>(&self) -> Option<&T>
322    where
323        Self: 'static,
324        T: ?Sized + 'static,
325    {
326        Specialization::try_new_static().map(|spec| spec.specialize_ref(self))
327    }
328
329    /// Attempts to specialize `&mut Self` as `&mut T` for types without
330    /// lifetimes.
331    ///
332    /// Note that this method requires target type to implement
333    /// [`LifetimeFree`]. Use [`TrySpecialize::try_specialize_from_mut`] if your
334    /// target type doesn't implement [`LifetimeFree`] but source type does.
335    ///
336    /// The [`LifetimeFree`] trait is **not** automatically derived for all
337    /// lifetime-free types. The library only implements it for standard library
338    /// types that do not have any lifetime parameters.
339    ///
340    /// [`LifetimeFree`]: crate::LifetimeFree
341    #[inline]
342    fn try_specialize_mut<T>(&mut self) -> Option<&mut T>
343    where
344        T: ?Sized + LifetimeFree,
345    {
346        Specialization::try_new().map(|spec| spec.specialize_mut(self))
347    }
348
349    /// Attempts to specialize `&mut T` as `&mut Self` for types without
350    /// lifetimes.
351    ///
352    /// Note that this method requires source type to implement
353    /// [`LifetimeFree`]. Use [`TrySpecialize::try_specialize_mut`] if your
354    /// source type doesn't implement [`LifetimeFree`] but target type does.
355    /// The [`LifetimeFree`] trait is **not** automatically derived for all
356    /// lifetime-free types. The library only implements it for standard library
357    /// types that do not have any lifetime parameters.
358    ///
359    /// [`LifetimeFree`]: crate::LifetimeFree
360    #[inline]
361    fn try_specialize_from_mut<T>(other: &mut T) -> Option<&mut Self>
362    where
363        T: ?Sized + LifetimeFree,
364    {
365        Specialization::try_new().map(|spec| spec.rev().specialize_mut(other))
366    }
367
368    /// Attempts to specialize `&mut Self` as `&mut T` for static types.
369    ///
370    /// Note that this method requires both types to have `'static` lifetime,
371    /// but don't require any type to implement [`LifetimeFree`].
372    #[inline]
373    fn try_specialize_mut_static<T>(&mut self) -> Option<&mut T>
374    where
375        Self: 'static,
376        T: ?Sized + 'static,
377    {
378        Specialization::try_new_static().map(|spec| spec.specialize_mut(self))
379    }
380
381    /// Attempts to specialize `Self` as `T` ignoring lifetimes.
382    ///
383    /// Returns `T` as `Self` wrapped in `Ok` if `Self` and `T` types are
384    /// identical. Otherwise, it returns `T` wrapped in `Err`.
385    ///
386    /// # Safety
387    ///
388    /// This method doesn't validate type lifetimes. Lifetimes equality should
389    /// be validated separately.
390    ///
391    /// Calling this method for types with any differences in lifetimes between
392    /// `Self` and `T` types is *[undefined behavior]*.
393    ///
394    /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
395    ///
396    /// # Examples
397    ///
398    /// Generate a placeholder with non-default value for some types with
399    /// several `&'static T` types:
400    /// ```rust
401    /// use try_specialize::{Specialization, TrySpecialize};
402    ///
403    /// fn placeholder<T>() -> T
404    /// where
405    ///     T: Default,
406    /// {
407    ///     None.or_else(|| T::try_specialize_from(12_u8).ok())
408    ///         .or_else(|| T::try_specialize_from(234_u16).ok())
409    ///         .or_else(|| T::try_specialize_from(3456_u32).ok())
410    ///         .or_else(|| {
411    ///             // SAFETY: For any `'a` It is safe to specialize `&'static str`
412    ///             // as `&'a str`.
413    ///             unsafe { "dummy string".try_specialize_ignore_lifetimes() }.ok()
414    ///         })
415    ///         .or_else(|| {
416    ///             // Ensure that the slice is static.
417    ///             const DEFAULT: &'static [u8] = &[1, 2, 3, 4, 5];
418    ///             // SAFETY: For any `'a` It is safe to specialize `&'static [u8]`
419    ///             // as `&'a [u8]`.
420    ///             unsafe { DEFAULT.try_specialize_ignore_lifetimes() }.ok()
421    ///         })
422    ///         .unwrap_or_default()
423    /// }
424    ///
425    /// assert_eq!(placeholder::<(u8, u8)>(), (0, 0));
426    /// assert_eq!(placeholder::<u32>(), 3456);
427    /// assert_eq!(placeholder::<f64>(), 0.0);
428    /// assert_eq!(placeholder::<&'static str>(), "dummy string");
429    /// assert_eq!(placeholder::<&'static [u8]>(), &[1, 2, 3, 4, 5]);
430    /// ```
431    #[expect(clippy::missing_errors_doc, reason = "already described")]
432    #[inline]
433    unsafe fn try_specialize_ignore_lifetimes<T>(self) -> Result<T, Self>
434    where
435        Self: Sized,
436    {
437        if let Some(spec) = Specialization::try_new_ignore_lifetimes() {
438            Ok(spec.specialize(self))
439        } else {
440            Err(self)
441        }
442    }
443
444    /// Attempts to specialize `&Self` as `&T` ignoring lifetimes.
445    ///
446    /// # Safety
447    ///
448    /// This method doesn't validate type lifetimes. Lifetimes equality should
449    /// be validated separately.
450    ///
451    /// Calling this method for types with any differences in lifetimes between
452    /// `Self` and `T` types is *[undefined behavior]*.
453    ///
454    /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
455    #[inline]
456    unsafe fn try_specialize_ref_ignore_lifetimes<T>(&self) -> Option<&T>
457    where
458        T: ?Sized,
459    {
460        Specialization::try_new_ignore_lifetimes().map(|spec| spec.specialize_ref(self))
461    }
462
463    /// Attempts to specialize `&mut Self` as `&mut T` ignoring lifetimes.
464    ///
465    /// # Safety
466    ///
467    /// This method doesn't validate type lifetimes. Lifetimes equality should
468    /// be validated separately.
469    ///
470    /// Calling this method for types with any differences in lifetimes between
471    /// `Self` and `T` types is *[undefined behavior]*.
472    ///
473    /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
474    #[inline]
475    unsafe fn try_specialize_mut_ignore_lifetimes<T>(&mut self) -> Option<&mut T>
476    where
477        T: ?Sized,
478    {
479        Specialization::try_new_ignore_lifetimes().map(|spec| spec.specialize_mut(self))
480    }
481}
482
483impl<T> TrySpecialize for T where T: ?Sized {}
484
485#[cfg(test)]
486mod tests {
487    #[cfg(feature = "alloc")]
488    use alloc::borrow::{Cow, ToOwned};
489    #[cfg(feature = "alloc")]
490    use alloc::boxed::Box;
491    #[cfg(feature = "alloc")]
492    use alloc::format;
493    #[cfg(feature = "alloc")]
494    use alloc::string::String;
495    #[cfg(feature = "alloc")]
496    use core::fmt::Display;
497
498    #[cfg(feature = "alloc")]
499    use crate::LifetimeFree;
500    use crate::{type_eq_ignore_lifetimes, TrySpecialize};
501
502    #[cfg(feature = "alloc")]
503    fn specialized_to_string<T>(value: T) -> String
504    where
505        T: LifetimeFree + Display,
506    {
507        match value.try_specialize::<i32>() {
508            Ok(value) => format!("{value}: i32"),
509            Err(value) => match value.try_specialize::<u32>() {
510                Ok(value) => format!("{value}: u32"),
511                Err(value) => format!("{value}: ???"),
512            },
513        }
514    }
515
516    #[test]
517    fn test_try_specialize() {
518        assert_eq!((123_i32).try_specialize::<i32>(), Ok(123_i32));
519        assert_eq!((123_u32).try_specialize::<u32>(), Ok(123_u32));
520        assert_eq!((123_i32).try_specialize::<u32>(), Err(123_i32));
521        assert_eq!("123".try_specialize::<i32>(), Err("123"));
522
523        assert_eq!(<u32>::try_specialize_from(123_u32), Ok(123_u32));
524        assert_eq!(<&'static str>::try_specialize_from(123), Err(123));
525    }
526
527    #[test]
528    fn test_try_specialize_ref() {
529        let value = &[1_u32, 2, 3][..];
530        assert_eq!(value.try_specialize_ref::<[u32]>(), Some(value));
531        assert_eq!(value.try_specialize_ref::<[i32]>(), None);
532        assert_eq!(value.try_specialize_ref::<str>(), None);
533        assert_eq!(value.try_specialize_ref::<str>(), None);
534
535        assert_eq!(<[u32]>::try_specialize_from_ref(value), Some(value));
536        assert_eq!(<&'static str>::try_specialize_from_ref(value), None);
537    }
538
539    #[test]
540    fn test_try_specialize_static() {
541        let value: &'static [&'static u32] = &[&1, &2, &3];
542        assert_eq!(value.try_specialize_static::<&[&u32]>(), Ok(value));
543        assert_eq!(value.try_specialize_static::<&[&i32]>(), Err(value));
544
545        let value: [&'static u32; 3] = [&1, &2, &3];
546        let value: &[&'static u32] = &value; // Reference itself it not 'static.
547        assert_eq!(value.try_specialize_ref_static::<[&u32]>(), Some(value));
548        assert_eq!(value.try_specialize_ref_static::<[&i32]>(), None);
549
550        let mut value: [&'static u32; 3] = [&1, &2, &3];
551        let value: &mut [&'static u32] = &mut value; // Reference itself it not 'static.
552        assert_eq!(
553            value.try_specialize_mut_static::<[&u32]>(),
554            Some(&mut [&1, &2, &3][..])
555        );
556        assert_eq!(value.try_specialize_mut_static::<[&i32]>(), None);
557    }
558
559    #[cfg(feature = "alloc")]
560    #[test]
561    fn test_try_specialize_ignore_lifetimes() {
562        // SAFETY: In real code the developer should ensure that `T1` and `T2`
563        // types have same lifetimes.
564        unsafe fn try_spec_erased<T1, T2>(value: T1) -> Result<T2, T1> {
565            value.try_specialize_ignore_lifetimes()
566        }
567
568        fn is_foobar_cow<'a, T>(value: Cow<'a, T>) -> bool
569        where
570            T: ?Sized + ToOwned,
571        {
572            // SAFETY: Specialization from `Cow<'a, T>` to `Cow<'a, str>`
573            // will always have equal lifetimes because `str` is [`LifetimeFree`].
574            unsafe {
575                try_spec_erased::<_, Cow<'a, str>>(value).is_ok_and(|value| value == "foobar")
576            }
577        }
578
579        let value = String::from("foo") + "bar";
580        let value = Cow::Borrowed(value.as_str());
581        assert!(is_foobar_cow(value));
582        assert!(!is_foobar_cow(Cow::Borrowed("foo")));
583        assert!(!is_foobar_cow(Cow::Borrowed(&123)));
584    }
585
586    #[cfg(feature = "alloc")]
587    #[test]
588    fn test_try_specialize_ref_ignore_lifetimes() {
589        #[expect(
590            clippy::redundant_allocation,
591            reason = "`Box` type is passed on purpose."
592        )]
593        fn with_non_static_box<'a>(mut value: Box<&'a u32>) {
594            let mut expected = value.clone();
595            assert_eq!(
596                // SAFETY: Okay in test.
597                unsafe {
598                    value
599                        .clone()
600                        .try_specialize_ignore_lifetimes::<Box<&'a u32>>()
601                },
602                Ok(expected.clone())
603            );
604
605            assert_eq!(
606                // SAFETY: Okay in test.
607                unsafe { value.try_specialize_ref_ignore_lifetimes::<Box<&'a u32>>() },
608                Some(&expected)
609            );
610
611            assert_eq!(
612                // SAFETY: Okay in test.
613                unsafe { value.try_specialize_mut_ignore_lifetimes::<Box<&'a u32>>() },
614                Some(&mut expected)
615            );
616        }
617
618        let mut value = 12;
619        value += 23;
620        let value: Box<&u32> = Box::new(&value);
621        with_non_static_box(value);
622    }
623
624    #[test]
625    fn test_try_specialize_mut() {
626        let value1 = &mut [1_u32, 2, 3][..];
627        let value2 = &mut [1_u32, 2, 3][..];
628        assert_eq!(value1.try_specialize_mut::<[u32]>(), Some(value2));
629        assert_eq!(value1.try_specialize_mut::<[i32]>(), None);
630        assert_eq!(value1.try_specialize_mut::<str>(), None);
631        assert_eq!(value1.try_specialize_mut::<str>(), None);
632
633        let value2 = &mut [1_u32, 2, 3][..];
634        assert_eq!(<[u32]>::try_specialize_from_mut(value1), Some(value2));
635        assert_eq!(<&'static str>::try_specialize_from_mut(value1), None);
636    }
637
638    #[cfg(feature = "alloc")]
639    #[test]
640    fn test_alloc_try_specialize() {
641        assert_eq!(specialized_to_string(123_i32), "123: i32");
642        assert_eq!(specialized_to_string(234_u32), "234: u32");
643        assert_eq!(specialized_to_string(345_i16), "345: ???");
644        assert_eq!(specialized_to_string(456_u16), "456: ???");
645    }
646
647    #[test]
648    fn test_should_not_impl_try_specialize_static_with_non_static_target() {
649        #[expect(clippy::trivially_copy_pass_by_ref, reason = "intentionally")]
650        fn scoped<'a>(_: &'a u32) {
651            type LocalFn<'a> = fn(&'a str) -> u32;
652            type StaticFn = LocalFn<'static>;
653
654            // Types `TypeId` are equal.
655            assert!(type_eq_ignore_lifetimes::<StaticFn, LocalFn<'a>>());
656
657            // But you can convert non-`'static` fn to `'static` one.
658            let func: Option<LocalFn<'a>> = None;
659            #[expect(clippy::unwrap_used, reason = "okay in tests")]
660            let _func: Option<StaticFn> = func.try_specialize_static().unwrap();
661
662            // The reverse will fail to compile.
663            // let func: Option<StaticFn> = None;
664            // let func: Option<LocalFn<'a>> =
665            //     func.try_specialize_static().unwrap();
666        }
667
668        let value = 123;
669        scoped(&value);
670    }
671}