pub struct Specialization<T1, T2>(/* private fields */)
where
T1: ?Sized,
T2: ?Sized;Expand description
A zero-sized marker struct that guarantees type equality between T1 and
T2.
This struct provides a type-safe mechanism to specialize one type into
another, enforcing that T1 and T2 are identical concrete types. It can
only be instantiated when both types are the same.
Specialization trait refers to a lower-level API. Prefer to use
TrySpecialize trait methods if applicable.
Library tests ensure that the specializations are fully optimized and
become zero-cost with opt-level >= 1. Note that release profile uses
opt-level = 3 by default.
Constructors cheat sheet:
| Type bounds | Constructor |
|---|---|
T1: 'static + LifetimeFree | try_new |
T2: 'static + LifetimeFree | try_new + rev |
T1: 'static, T2: 'static | try_new_static |
underlying type maybe impls LifetimeFree | try_new_if_lifetime_free_weak |
unsafe, without lifetimes check | try_new_ignore_lifetimes |
Specialization methods cheat sheet:
| From | To | Method |
|---|---|---|
T1 | T2 | specialize |
&T1 | &T2 | specialize_ref |
&mut T1 | &mut T2 | specialize_mut |
T2 | T1 | rev + specialize |
Wrapper<T1> | Wrapper<T2> | map + specialize |
T1::AssociatedType | T2::AssociatedType | map + specialize |
Implementations§
source§impl<T1, T2> Specialization<T1, T2>
impl<T1, T2> Specialization<T1, T2>
sourcepub fn try_new() -> Option<Self>where
T2: LifetimeFree,
pub fn try_new() -> Option<Self>where
T2: LifetimeFree,
Checks the types T1 and T2 for equality and returns the
specialization provider if types are equal.
Note that this method requires source type to implement LifetimeFree.
Use Specialization::try_new().rev() method to check the target
type instead.
LifetimeFree is not automatically derived and implemented only for a
set of types without lifetimes.
For simple cases consider using TrySpecialize methods like
try_specialize,
try_specialize_ref, and
try_specialize_mut instead.
You can use Specialization::try_new_static if both types are
'static.
§Examples
Same-type stringifiable type concatenation, that don’t allocate memory
if one of the arguments is empty &str:
use core::fmt::Display;
use std::borrow::Cow;
use std::format;
use try_specialize::Specialization;
fn concat_same<'a, T>(first: &'a T, second: &'a T) -> Cow<'a, str>
where
T: ?Sized + Display,
{
if let Some(spec) = Specialization::<T, str>::try_new() {
match (spec.specialize_ref(first), spec.specialize_ref(second)) {
(first, "") => Cow::Borrowed(first),
("", second) => Cow::Borrowed(second),
(first, second) => Cow::Owned([first, second].concat()),
}
} else {
Cow::Owned(format!("{first}{second}"))
}
}
assert!(matches!(concat_same("foo", "bar"), Cow::Owned(v) if v == "foobar"));
assert!(matches!(concat_same("foo", ""), Cow::Borrowed("foo")));
assert!(matches!(concat_same("", "bar"), Cow::Borrowed("bar")));
let foo = String::from("foo");
let bar = String::from("bar");
assert!(matches!(concat_same(&foo, &bar), Cow::Owned(v) if v == "foobar"));
assert!(matches!(concat_same(&123, &456), Cow::Owned(v) if v == "123456"));Generate a placeholder with non-default value for some types:
use try_specialize::Specialization;
fn placeholder<T>() -> T
where
T: Default,
{
if let Some(spec) = Specialization::<T, u8>::try_new() {
spec.rev().specialize(12)
} else if let Some(spec) = Specialization::<T, u32>::try_new() {
spec.rev().specialize(42)
} else if let Some(spec) = Specialization::<T, f64>::try_new() {
spec.rev().specialize(123.456)
// ...
} else {
T::default()
}
}
assert_eq!(placeholder::<&'static str>(), "");
assert_eq!(placeholder::<u8>(), 12);
assert_eq!(placeholder::<(u8, u8)>(), (0, 0));
assert_eq!(placeholder::<u32>(), 42);
assert_eq!(placeholder::<f64>(), 123.456);
assert_eq!(placeholder::<i128>(), 0);sourcepub fn try_new_static() -> Option<Self>where
T1: 'static,
T2: 'static,
pub fn try_new_static() -> Option<Self>where
T1: 'static,
T2: 'static,
Checks the types T1 and T2 for equality and returns the
specialization provider if types are equal.
Note that this method requires both types to have 'static lifetime,
but don’t require any type to implement LifetimeFree.
For simple cases consider using TrySpecialize methods like
try_specialize_static,
try_specialize_ref_static, and
try_specialize_mut_static instead.
You can use Specialization::try_new if the target type
implements LifetimeFree trait or
Specialization::try_new().rev() if the source type implements
LifetimeFree trait.
§Examples
Collection reverse function with optimized specializations for Vec<T>
and Box<[T]>:
use core::sync::atomic::{AtomicU32, Ordering as AtomicOrdering};
use std::collections::VecDeque;
use try_specialize::Specialization;
static DEBUG_SPEC_USED: AtomicU32 = AtomicU32::new(0);
fn reverse_collection<T>(value: T) -> T
where
T: 'static + IntoIterator + FromIterator<T::Item>,
T::IntoIter: DoubleEndedIterator,
{
let spec1 = Specialization::<T, Vec<T::Item>>::try_new_static();
let spec2 = Specialization::<T, Box<[T::Item]>>::try_new_static();
if let Some(spec1) = spec1 {
DEBUG_SPEC_USED.store(101, AtomicOrdering::Relaxed);
let mut vec = spec1.specialize(value);
vec.reverse();
spec1.rev().specialize(vec)
} else if let Some(spec2) = spec2 {
DEBUG_SPEC_USED.store(202, AtomicOrdering::Relaxed);
let mut boxed = spec2.specialize(value);
boxed.reverse();
spec2.rev().specialize(boxed)
} else {
DEBUG_SPEC_USED.store(303, AtomicOrdering::Relaxed);
value.into_iter().rev().collect()
}
}
assert_eq!(reverse_collection(vec![1, 2, 3]), vec![3, 2, 1]);
assert_eq!(DEBUG_SPEC_USED.load(AtomicOrdering::Relaxed), 101);
assert_eq!(
reverse_collection(vec![1, 2, 3].into_boxed_slice()),
vec![3, 2, 1].into_boxed_slice()
);
assert_eq!(DEBUG_SPEC_USED.load(AtomicOrdering::Relaxed), 202);
assert_eq!(
reverse_collection(VecDeque::from([1, 2, 3])),
VecDeque::from([3, 2, 1])
);
assert_eq!(DEBUG_SPEC_USED.load(AtomicOrdering::Relaxed), 303);Same-type stringifiable type concatenation, that don’t allocate memory
if one of the arguments is empty &'static str and avoid reallocations
if one of the arguments is empty String:
use core::fmt::Display;
use std::borrow::Cow;
use try_specialize::Specialization;
fn concat_same<T>(first: T, second: T) -> Cow<'static, str>
where
T: 'static + Display,
{
if let Some(spec) = Specialization::<T, &'static str>::try_new_static() {
match (spec.specialize(first), spec.specialize(second)) {
(first, "") => Cow::Borrowed(first),
("", second) => Cow::Borrowed(second),
(first, second) => Cow::Owned([first, second].concat()),
}
} else if let Some(spec) = Specialization::<T, String>::try_new_static() {
let first = spec.specialize(first);
let second = spec.specialize(second);
match (first.is_empty(), second.is_empty()) {
(false | true, true) => Cow::Owned(first),
(true, false) => Cow::Owned(second),
(false, false) => Cow::Owned(first + &second),
}
} else {
Cow::Owned(format!("{first}{second}"))
}
}
assert!(matches!(concat_same("foo", "bar"), Cow::Owned(v) if v == "foobar"));
assert!(matches!(concat_same("foo", ""), Cow::Borrowed("foo")));
assert!(matches!(concat_same("", "bar"), Cow::Borrowed("bar")));
let foo = String::from("foo");
let bar = String::from("bar");
assert!(matches!(concat_same(foo, bar), Cow::Owned(v) if v == "foobar"));
let empty = String::new();
let bar = String::from("bar");
assert!(matches!(concat_same(empty, bar), Cow::Owned(v) if v == "bar"));
let foo = String::from("foo");
let empty = String::new();
assert!(matches!(concat_same(foo, empty), Cow::Owned(v) if v == "foo"));
assert!(matches!(concat_same(123, 456), Cow::Owned(v) if v == "123456"));Generate a placeholder with non-default value for some types:
use try_specialize::Specialization;
fn placeholder<T>() -> T
where
T: 'static + Default,
{
if let Some(spec) = Specialization::<T, &'static str>::try_new_static() {
spec.rev().specialize("dummy string")
} else if let Some(spec) = Specialization::<T, u32>::try_new() {
spec.rev().specialize(42)
} else if let Some(spec) = Specialization::<T, f64>::try_new() {
spec.rev().specialize(123.456)
// ...
} else {
T::default()
}
}
assert_eq!(placeholder::<&'static str>(), "dummy string");
assert_eq!(placeholder::<(u8, u8)>(), (0, 0));
assert_eq!(placeholder::<u32>(), 42);
assert_eq!(placeholder::<f64>(), 123.456);
assert_eq!(placeholder::<i128>(), 0);sourcepub unsafe fn try_new_ignore_lifetimes() -> Option<Self>
pub unsafe fn try_new_ignore_lifetimes() -> Option<Self>
Checks the types T1 and T2 for equality and returns the
specialization provider if the types are equal ignoring lifetimes.
For simple cases consider using TrySpecialize methods like
try_specialize_ignore_lifetimes,
try_specialize_ref_ignore_lifetimes, and
try_specialize_mut_ignore_lifetimes instead.
§Safety
This method doesn’t validate type lifetimes. Lifetimes equality should be validated separately.
§Examples
Specialized to third-party library item that can definitely be
LifetimeFree.
mod third_party_lib {
#[derive(Eq, PartialEq, Default, Debug)]
pub struct Marker(pub u32);
}
use third_party_lib::Marker;
use try_specialize::Specialization;
fn inc_if_marker<T>(value: T) -> T {
// SAFETY: `Marker` type has no lifetime parameters.
if let Some(spec) = unsafe { Specialization::<T, Marker>::try_new_ignore_lifetimes() } {
spec.rev().specialize(Marker(spec.specialize(value).0 + 1))
} else {
value
}
}
assert_eq!(inc_if_marker(123), 123);
assert_eq!(inc_if_marker("str"), "str");
assert_eq!(inc_if_marker(Marker(123)), Marker(124));sourcepub const unsafe fn new_unchecked() -> Self
pub const unsafe fn new_unchecked() -> Self
Construct a new Specialization<T1, T2> without any types
equality checks.
§Safety
Calling this method for Specialization<T1, T2> with different
types in T1 and T2 is undefined behavior.
sourcepub const fn rev(&self) -> Specialization<T2, T1>
pub const fn rev(&self) -> Specialization<T2, T1>
Reverses the specialization.
It can be used to convert the target type back to source type and also
to create a specialization from a type which implements LifetimeFree,
for example: Specialization::<u8, T>::new().rev().
For simple cases consider using TrySpecialize methods like
try_specialize_from,
try_specialize_from_ref, and
try_specialize_from_mut instead.
§Examples
Synthetic example with custom behavior for u32 type:
use try_specialize::{LifetimeFree, Specialization};
fn inc_if_u32<T>(value: T) -> T
where
T: LifetimeFree,
{
if let Some(spec) = Specialization::<T, u32>::try_new() {
spec.rev().specialize(spec.specialize(value) + 1)
} else {
value
}
}
assert_eq!(inc_if_u32(123_u32), 124);
assert_eq!(inc_if_u32(123_i32), 123);
assert_eq!(inc_if_u32(123_u8), 123);More realistic example can be found at
examples/encode.rs.
sourcepub const fn map<M>(
&self,
) -> Specialization<<M as TypeFn<T1>>::Output, <M as TypeFn<T2>>::Output>
pub const fn map<M>( &self, ) -> Specialization<<M as TypeFn<T1>>::Output, <M as TypeFn<T2>>::Output>
Maps the specialization types to other types using a specified
TypeFn.
This can be useful to specialize third-party wrappers or the trait
associated types if they are based on LifetimeFree types.
§Examples
Simplified custom vec-like type processing optimization example:
mod third_party_lib {
pub struct CustomVec<T> {
// ...
}
}
use third_party_lib::CustomVec;
use try_specialize::{Specialization, TypeFn};
fn process<T>(data: CustomVec<T>) {
struct MapIntoCustomVec;
impl<T> TypeFn<T> for MapIntoCustomVec {
type Output = CustomVec<T>;
}
if let Some(spec) = Specialization::<T, u8>::try_new() {
let data: CustomVec<u8> = spec.map::<MapIntoCustomVec>().specialize(data);
// optimized specialized implementation...
} else {
// default implementation...
}
}Simplified custom hashbrown::HashMap type processing optimization
example with multiple type generics:
use hashbrown::HashMap;
use try_specialize::{Specialization, TypeFn};
fn process<K, V>(data: HashMap<K, V>)
where
K: 'static,
V: 'static,
{
struct MapIntoHashMap;
impl<K, V> TypeFn<(K, V)> for MapIntoHashMap {
type Output = HashMap<K, V>;
}
if let Some(spec) = Specialization::<(K, V), (char, char)>::try_new_static() {
let data: HashMap<char, char> = spec.map::<MapIntoHashMap>().specialize(data);
// optimized specialized implementation...
} else if let Some(spec) = Specialization::<(K, V), (String, String)>::try_new_static() {
let data: HashMap<String, String> = spec.map::<MapIntoHashMap>().specialize(data);
// optimized specialized implementation...
} else {
// default implementation...
}
}Custom data encoders and decoders with customizable per-type encoding
and decoding errors and optimized byte array encoding and decoding.
Full example code is available at
examples/encode.rs.
// ...
impl<T> Encode for [T]
where
T: Encode,
{
type EncodeError = T::EncodeError;
#[inline]
fn encode_to<W>(&self, writer: &mut W) -> Result<(), Self::EncodeError>
where
W: ?Sized + Write,
{
if let Some(spec) = Specialization::<[T], [u8]>::try_new() {
// Specialize self from `[T; N]` to `[u32; N]`
let bytes: &[u8] = spec.specialize_ref(self);
// Map type specialization to its associated error specialization.
let spec_err = spec.rev().map::<MapToEncodeError>();
writer
.write_all(bytes)
// Specialize error from `io::Error` to `Self::EncodeError`.
.map_err(|err| spec_err.specialize(err))?;
} else {
for item in self {
item.encode_to(writer)?;
}
}
Ok(())
}
}
// ...sourcepub fn specialize(&self, value: T1) -> T2
pub fn specialize(&self, value: T1) -> T2
Infallibly specialize value with type T1 as T2.
This method can only be called for a Specialization<T1, T2> whose
existing instance indicates that types T1 and T2 are equivalent.
For simple cases consider using TrySpecialize methods like
try_specialize,
try_specialize_from, and
try_specialize_static instead.
sourcepub const fn specialize_ref<'a>(&self, value: &'a T1) -> &'a T2
pub const fn specialize_ref<'a>(&self, value: &'a T1) -> &'a T2
Infallibly specialize value with type &T1 as &T2.
This method can only be called for a Specialization<T1, T2> whose
existing instance indicates that types T1 and T2 are equivalent.
For simple cases consider using TrySpecialize methods like
try_specialize_ref,
try_specialize_from_ref, and
try_specialize_ref_static instead.
sourcepub fn specialize_mut<'a>(&self, value: &'a mut T1) -> &'a mut T2
pub fn specialize_mut<'a>(&self, value: &'a mut T1) -> &'a mut T2
Infallibly specialize value with type &mut T1 as &mut T2.
This method can only be called for a Specialization<T1, T2> whose
existing instance indicates that types T1 and T2 are equivalent.
For simple cases consider using TrySpecialize methods like
try_specialize_mut,
try_specialize_from_mut, and
try_specialize_mut_static instead.
Trait Implementations§
source§impl<T1, T2> Clone for Specialization<T1, T2>
impl<T1, T2> Clone for Specialization<T1, T2>
source§impl<T1, T2> Debug for Specialization<T1, T2>
impl<T1, T2> Debug for Specialization<T1, T2>
source§impl<T1, T2> Hash for Specialization<T1, T2>
impl<T1, T2> Hash for Specialization<T1, T2>
source§impl<T1, T2> Ord for Specialization<T1, T2>
impl<T1, T2> Ord for Specialization<T1, T2>
source§impl<T1, T2> PartialEq for Specialization<T1, T2>
impl<T1, T2> PartialEq for Specialization<T1, T2>
source§impl<T1, T2> PartialOrd for Specialization<T1, T2>
impl<T1, T2> PartialOrd for Specialization<T1, T2>
source§impl<T1, T2> WeakSpecialization for Specialization<T1, T2>
Available on crate features alloc and unreliable only.
impl<T1, T2> WeakSpecialization for Specialization<T1, T2>
alloc and unreliable only.source§fn try_new_if_lifetime_free_weak() -> Option<Self>
fn try_new_if_lifetime_free_weak() -> Option<Self>
T1 and T2 for equality and returns the
specialization provider if types implement LifetimeFree and the types
are equal. Read moreimpl<T1, T2> Copy for Specialization<T1, T2>
impl<T1, T2> Eq for Specialization<T1, T2>
Auto Trait Implementations§
impl<T1, T2> Freeze for Specialization<T1, T2>
impl<T1, T2> RefUnwindSafe for Specialization<T1, T2>
impl<T1, T2> Send for Specialization<T1, T2>
impl<T1, T2> Sync for Specialization<T1, T2>
impl<T1, T2> Unpin for Specialization<T1, T2>
impl<T1, T2> UnwindSafe for Specialization<T1, T2>
Blanket Implementations§
source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
source§unsafe fn clone_to_uninit(&self, dst: *mut T)
unsafe fn clone_to_uninit(&self, dst: *mut T)
clone_to_uninit)source§impl<T> TrySpecialize for Twhere
T: ?Sized,
impl<T> TrySpecialize for Twhere
T: ?Sized,
source§fn try_specialize<T>(self) -> Result<T, Self>where
Self: Sized,
T: LifetimeFree,
fn try_specialize<T>(self) -> Result<T, Self>where
Self: Sized,
T: LifetimeFree,
source§fn try_specialize_from<T>(other: T) -> Result<Self, T>where
Self: Sized,
T: LifetimeFree,
fn try_specialize_from<T>(other: T) -> Result<Self, T>where
Self: Sized,
T: LifetimeFree,
source§fn try_specialize_static<T>(self) -> Result<T, Self>where
Self: 'static + Sized,
T: 'static,
fn try_specialize_static<T>(self) -> Result<T, Self>where
Self: 'static + Sized,
T: 'static,
source§fn try_specialize_ref<T>(&self) -> Option<&T>where
T: ?Sized + LifetimeFree,
fn try_specialize_ref<T>(&self) -> Option<&T>where
T: ?Sized + LifetimeFree,
source§fn try_specialize_from_ref<T>(other: &T) -> Option<&Self>where
T: ?Sized + LifetimeFree,
fn try_specialize_from_ref<T>(other: &T) -> Option<&Self>where
T: ?Sized + LifetimeFree,
source§fn try_specialize_ref_static<T>(&self) -> Option<&T>where
Self: 'static,
T: ?Sized + 'static,
fn try_specialize_ref_static<T>(&self) -> Option<&T>where
Self: 'static,
T: ?Sized + 'static,
source§fn try_specialize_mut<T>(&mut self) -> Option<&mut T>where
T: ?Sized + LifetimeFree,
fn try_specialize_mut<T>(&mut self) -> Option<&mut T>where
T: ?Sized + LifetimeFree,
source§fn try_specialize_from_mut<T>(other: &mut T) -> Option<&mut Self>where
T: ?Sized + LifetimeFree,
fn try_specialize_from_mut<T>(other: &mut T) -> Option<&mut Self>where
T: ?Sized + LifetimeFree,
source§fn try_specialize_mut_static<T>(&mut self) -> Option<&mut T>where
Self: 'static,
T: ?Sized + 'static,
fn try_specialize_mut_static<T>(&mut self) -> Option<&mut T>where
Self: 'static,
T: ?Sized + 'static,
source§unsafe fn try_specialize_ignore_lifetimes<T>(self) -> Result<T, Self>where
Self: Sized,
unsafe fn try_specialize_ignore_lifetimes<T>(self) -> Result<T, Self>where
Self: Sized,
source§impl<T> TrySpecializeWeak for Twhere
T: ?Sized,
impl<T> TrySpecializeWeak for Twhere
T: ?Sized,
source§fn try_specialize_if_lifetime_free_weak<T>(self) -> Result<T, Self>where
Self: Sized,
fn try_specialize_if_lifetime_free_weak<T>(self) -> Result<T, Self>where
Self: Sized,
alloc and unreliable only.Self as T checking that underlying Self
type implements LifetimeFree. Read moresource§fn try_specialize_ref_if_lifetime_free_weak<T>(&self) -> Option<&T>where
T: ?Sized,
fn try_specialize_ref_if_lifetime_free_weak<T>(&self) -> Option<&T>where
T: ?Sized,
alloc and unreliable only.&Self as &T checking that underlying Self
type implements LifetimeFree. Read moresource§fn try_specialize_mut_if_lifetime_free_weak<T>(&mut self) -> Option<&mut T>where
T: ?Sized,
fn try_specialize_mut_if_lifetime_free_weak<T>(&mut self) -> Option<&mut T>where
T: ?Sized,
alloc and unreliable only.&mut Self as &mut T checking that underlying
Self type implements LifetimeFree. Read more