use crate::{LifetimeFree, Specialization};
pub trait TrySpecialize {
#[expect(clippy::missing_errors_doc, reason = "already described")]
#[inline]
fn try_specialize<T>(self) -> Result<T, Self>
where
Self: Sized,
T: LifetimeFree,
{
if let Some(spec) = Specialization::try_new() {
Ok(spec.specialize(self))
} else {
Err(self)
}
}
#[expect(clippy::missing_errors_doc, reason = "already described")]
#[inline]
fn try_specialize_from<T>(other: T) -> Result<Self, T>
where
Self: Sized,
T: LifetimeFree,
{
if let Some(spec) = Specialization::try_new() {
Ok(spec.rev().specialize(other))
} else {
Err(other)
}
}
#[expect(clippy::missing_errors_doc, reason = "already described")]
#[inline]
fn try_specialize_static<T>(self) -> Result<T, Self>
where
Self: 'static + Sized,
T: 'static,
{
if let Some(spec) = Specialization::try_new_static() {
Ok(spec.specialize(self))
} else {
Err(self)
}
}
#[inline]
fn try_specialize_ref<T>(&self) -> Option<&T>
where
T: ?Sized + LifetimeFree,
{
Specialization::try_new().map(|spec| spec.specialize_ref(self))
}
#[inline]
fn try_specialize_from_ref<T>(other: &T) -> Option<&Self>
where
T: ?Sized + LifetimeFree,
{
Specialization::try_new().map(|spec| spec.rev().specialize_ref(other))
}
#[inline]
fn try_specialize_ref_static<T>(&self) -> Option<&T>
where
Self: 'static,
T: ?Sized + 'static,
{
Specialization::try_new_static().map(|spec| spec.specialize_ref(self))
}
#[inline]
fn try_specialize_mut<T>(&mut self) -> Option<&mut T>
where
T: ?Sized + LifetimeFree,
{
Specialization::try_new().map(|spec| spec.specialize_mut(self))
}
#[inline]
fn try_specialize_from_mut<T>(other: &mut T) -> Option<&mut Self>
where
T: ?Sized + LifetimeFree,
{
Specialization::try_new().map(|spec| spec.rev().specialize_mut(other))
}
#[inline]
fn try_specialize_mut_static<T>(&mut self) -> Option<&mut T>
where
Self: 'static,
T: ?Sized + 'static,
{
Specialization::try_new_static().map(|spec| spec.specialize_mut(self))
}
#[expect(clippy::missing_errors_doc, reason = "already described")]
#[inline]
unsafe fn try_specialize_ignore_lifetimes<T>(self) -> Result<T, Self>
where
Self: Sized,
{
if let Some(spec) = Specialization::try_new_ignore_lifetimes() {
Ok(spec.specialize(self))
} else {
Err(self)
}
}
#[inline]
unsafe fn try_specialize_ref_ignore_lifetimes<T>(&self) -> Option<&T>
where
T: ?Sized,
{
Specialization::try_new_ignore_lifetimes().map(|spec| spec.specialize_ref(self))
}
#[inline]
unsafe fn try_specialize_mut_ignore_lifetimes<T>(&mut self) -> Option<&mut T>
where
T: ?Sized,
{
Specialization::try_new_ignore_lifetimes().map(|spec| spec.specialize_mut(self))
}
}
impl<T> TrySpecialize for T where T: ?Sized {}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use alloc::borrow::{Cow, ToOwned};
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
use alloc::format;
#[cfg(feature = "alloc")]
use alloc::string::String;
#[cfg(feature = "alloc")]
use core::fmt::Display;
#[cfg(feature = "alloc")]
use crate::LifetimeFree;
use crate::{type_eq_ignore_lifetimes, TrySpecialize};
#[cfg(feature = "alloc")]
fn specialized_to_string<T>(value: T) -> String
where
T: LifetimeFree + Display,
{
match value.try_specialize::<i32>() {
Ok(value) => format!("{value}: i32"),
Err(value) => match value.try_specialize::<u32>() {
Ok(value) => format!("{value}: u32"),
Err(value) => format!("{value}: ???"),
},
}
}
#[test]
fn test_try_specialize() {
assert_eq!((123_i32).try_specialize::<i32>(), Ok(123_i32));
assert_eq!((123_u32).try_specialize::<u32>(), Ok(123_u32));
assert_eq!((123_i32).try_specialize::<u32>(), Err(123_i32));
assert_eq!("123".try_specialize::<i32>(), Err("123"));
assert_eq!(<u32>::try_specialize_from(123_u32), Ok(123_u32));
assert_eq!(<&'static str>::try_specialize_from(123), Err(123));
}
#[test]
fn test_try_specialize_ref() {
let value = &[1_u32, 2, 3][..];
assert_eq!(value.try_specialize_ref::<[u32]>(), Some(value));
assert_eq!(value.try_specialize_ref::<[i32]>(), None);
assert_eq!(value.try_specialize_ref::<str>(), None);
assert_eq!(value.try_specialize_ref::<str>(), None);
assert_eq!(<[u32]>::try_specialize_from_ref(value), Some(value));
assert_eq!(<&'static str>::try_specialize_from_ref(value), None);
}
#[test]
fn test_try_specialize_static() {
let value: &'static [&'static u32] = &[&1, &2, &3];
assert_eq!(value.try_specialize_static::<&[&u32]>(), Ok(value));
assert_eq!(value.try_specialize_static::<&[&i32]>(), Err(value));
let value: [&'static u32; 3] = [&1, &2, &3];
let value: &[&'static u32] = &value; assert_eq!(value.try_specialize_ref_static::<[&u32]>(), Some(value));
assert_eq!(value.try_specialize_ref_static::<[&i32]>(), None);
let mut value: [&'static u32; 3] = [&1, &2, &3];
let value: &mut [&'static u32] = &mut value; assert_eq!(
value.try_specialize_mut_static::<[&u32]>(),
Some(&mut [&1, &2, &3][..])
);
assert_eq!(value.try_specialize_mut_static::<[&i32]>(), None);
}
#[cfg(feature = "alloc")]
#[test]
fn test_try_specialize_ignore_lifetimes() {
unsafe fn try_spec_erased<T1, T2>(value: T1) -> Result<T2, T1> {
value.try_specialize_ignore_lifetimes()
}
fn is_foobar_cow<'a, T>(value: Cow<'a, T>) -> bool
where
T: ?Sized + ToOwned,
{
unsafe {
try_spec_erased::<_, Cow<'a, str>>(value).is_ok_and(|value| value == "foobar")
}
}
let value = String::from("foo") + "bar";
let value = Cow::Borrowed(value.as_str());
assert!(is_foobar_cow(value));
assert!(!is_foobar_cow(Cow::Borrowed("foo")));
assert!(!is_foobar_cow(Cow::Borrowed(&123)));
}
#[cfg(feature = "alloc")]
#[test]
fn test_try_specialize_ref_ignore_lifetimes() {
#[expect(
clippy::redundant_allocation,
reason = "`Box` type is passed on purpose."
)]
fn with_non_static_box<'a>(mut value: Box<&'a u32>) {
let mut expected = value.clone();
assert_eq!(
unsafe {
value
.clone()
.try_specialize_ignore_lifetimes::<Box<&'a u32>>()
},
Ok(expected.clone())
);
assert_eq!(
unsafe { value.try_specialize_ref_ignore_lifetimes::<Box<&'a u32>>() },
Some(&expected)
);
assert_eq!(
unsafe { value.try_specialize_mut_ignore_lifetimes::<Box<&'a u32>>() },
Some(&mut expected)
);
}
let mut value = 12;
value += 23;
let value: Box<&u32> = Box::new(&value);
with_non_static_box(value);
}
#[test]
fn test_try_specialize_mut() {
let value1 = &mut [1_u32, 2, 3][..];
let value2 = &mut [1_u32, 2, 3][..];
assert_eq!(value1.try_specialize_mut::<[u32]>(), Some(value2));
assert_eq!(value1.try_specialize_mut::<[i32]>(), None);
assert_eq!(value1.try_specialize_mut::<str>(), None);
assert_eq!(value1.try_specialize_mut::<str>(), None);
let value2 = &mut [1_u32, 2, 3][..];
assert_eq!(<[u32]>::try_specialize_from_mut(value1), Some(value2));
assert_eq!(<&'static str>::try_specialize_from_mut(value1), None);
}
#[cfg(feature = "alloc")]
#[test]
fn test_alloc_try_specialize() {
assert_eq!(specialized_to_string(123_i32), "123: i32");
assert_eq!(specialized_to_string(234_u32), "234: u32");
assert_eq!(specialized_to_string(345_i16), "345: ???");
assert_eq!(specialized_to_string(456_u16), "456: ???");
}
#[test]
fn test_should_not_impl_try_specialize_static_with_non_static_target() {
#[expect(clippy::trivially_copy_pass_by_ref, reason = "intentionally")]
fn scoped<'a>(_: &'a u32) {
type LocalFn<'a> = fn(&'a str) -> u32;
type StaticFn = LocalFn<'static>;
assert!(type_eq_ignore_lifetimes::<StaticFn, LocalFn<'a>>());
let func: Option<LocalFn<'a>> = None;
#[expect(clippy::unwrap_used, reason = "okay in tests")]
let _func: Option<StaticFn> = func.try_specialize_static().unwrap();
}
let value = 123;
scoped(&value);
}
}