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}