Trait syllogism::Specialize [−][src]
pub trait Specialize<T>: Sized {
fn specialize(self) -> Distinction<T, Self>;
}
Expand description
A trait allowing to distinguish between the special case and the generic case at run time.
See the crate level documentation for more info.
Below we describe various methods for implementing the Specialize
trait.
The easiest way however is probably to use the syllogism-macro
crate.
Implementing Specialize
using the IsNot
trait
One shortcut you can take if the data type has no type parameters is to use the IsNot
trait,
assuming you already need to implement this.
use syllogism::{IsNot, Specialize, Distinction};
struct T1 { /* ... */ }
struct T2 { /* ... */ }
struct T3 { /* ... */ }
impl IsNot<T2> for T1 {}
impl IsNot<T3> for T1 {}
impl IsNot<T1> for T2 {}
impl IsNot<T3> for T2 {}
impl IsNot<T1> for T3 {}
impl IsNot<T2> for T3 {}
impl<T> Specialize<T> for T1
where T: IsNot<T1>
{
fn specialize(self) -> Distinction<T, Self> {
Distinction::Generic(self)
}
}
impl Specialize<T1> for T1 {
fn specialize(self) -> Distinction<T1, Self> {
Distinction::Special(self)
}
}
// And similar for `T2` and `T3`.
Implementing Specialize
for types with a type parameter using the Specialize
trait
When a type has a type parameter, in some circumstances,
you can not use the IsNot
trait to implement
the Specialize
trait: the following code will fail to compile
use syllogism::{IsNot, Specialize, Distinction};
struct T1<T> {
t: T
// ...
}
impl<T, U> IsNot<T1<T>> for T1<U> where T: IsNot<U> {}
impl<T, U> Specialize<T1<T>> for T1<U> where T: IsNot<U>{
fn specialize(self) -> Distinction<T, Self> {
Distinction::Generic(self)
}
}
impl<T> Specialize<T1<T>> for T1<T> {
fn specialize(self) -> Distinction<T1<T>, Self> {
Distinction::Special(self)
}
}
The reason for this compile failure is that a type Ts
may implement IsNot<Ts>
and
in that case, T1<Ts>
implements IsNot<T1<Ts>>
and the impl blocks overlap.
This can be solved by using the Specialize
trait again:
use syllogism::{IsNot, Specialize, Distinction};
struct T1<T> {
t: T,
other_field: u8
}
impl<T, U> IsNot<T1<T>> for T1<U>
where T: IsNot<U> {
}
impl<T, U> Specialize<T1<T>> for T1<U>
where U: Specialize<T>
{
fn specialize(self) -> Distinction<T1<T>, Self> {
let T1{
t,
other_field
} = self; // deconstructing `let`
match t.specialize() {
Distinction::Special(special) => {
Distinction::Special(T1{t: special, other_field})
},
Distinction::Generic(generic) => {
Distinction::Generic(T1{t: generic, other_field})
}
}
}
}
Implementing Specialize
using macros
Implementing Specialize
correctly for each pair of data types at hand can be
error prone. E.g. if there are three data types at play, T1
, T2
, T3
.
If you want to allow specialization for any of these data types, giving the other two the
“default” treatment, then you need nine implementations of the Specialize
trait:
use syllogism::{Specialize, Distinction};
struct T1 { /* ... */ }
struct T2 { /* ... */ }
struct T3 { /* ... */ }
impl Specialize<T1> for T1 {
fn specialize(self) -> Distinction<T1, Self> {
Distinction::Special(self)
}
}
impl Specialize<T2> for T1 {
fn specialize(self) -> Distinction<T2, Self> {
Distinction::Generic(self)
}
}
impl Specialize<T3> for T1 {
// similar
}
impl Specialize<T1> for T2 {
// similar
}
impl Specialize<T2> for T2 {
// similar
}
impl Specialize<T3> for T2 {
// similar
}
impl Specialize<T1> for T3 {
// similar
}
impl Specialize<T2> for T3 {
// similar
}
impl Specialize<T3> for T3 {
// similar
}
The syllogism-macro
crate provides procedural macros to generate these impl
s for you,
and also offers some techniques that allow extending the set of data types across crate
boundaries. We refer to the documentation of that crate for more information.