[−][src]Crate syllogism
Utilities to allow for some specialization using stable Rust.
This crate provides two mechanisms for specialization:
- Using the
IsNot
trait. This can be used when the data-type that you want to syllogism for is a concrete type. - Using the
Specialize
trait.
When using the IsNot
trait, in order to syllogism for a data type T1
and handle another
data type T2
in a generic way, T2
needs to implement IsNot<T1>
.
When using the Specialize
trait, in order to syllogism for a data type T1
and handle
another data type T2
in a generic way, both T1
and T2
need to implement Specialize<T1>
.
You may need many impl blocks for implementing the IsNot
and the Specialize
trait.
In order to facilitate you to do so, you can use the define_compatibility
macro.
The syllogism-macro crate defines some procedural macros that help with these implementations, also when not all compatible data types are defined in the same crate.
Specialization using the IsNot
trait
Using the IsNot
trait is straightforward if you want to syllogism
for a concrete data type.
use syllogism::IsNot; trait GenericTrait<T> { // ... } struct SpecialDataType { /* ... */ } struct GenericDataType1 { /* ... */ } impl IsNot<SpecialDataType> for GenericDataType1 {} struct GenericDataType2 { /* ... */ } impl IsNot<SpecialDataType> for GenericDataType2 {} struct MyStruct { // ... } impl GenericTrait<SpecialDataType> for MyStruct { // The special implementation } // This can be used for the data types `GenericDataType1` and `GenericDataType2`. impl<T> GenericTrait<T> for MyStruct where T: IsNot<SpecialDataType> { // The generic implementation }
You can also have specialization for more than one type:
use syllogism::IsNot; // One special implementation: impl GenericTrait<SpecialDataType1> for MyStruct { // ... } // Another special implementation: impl GenericTrait<SpecialDataType2> for MyStruct { // ... } // The generic implementation. impl<T> GenericTrait<T> for MyStruct where T: IsNot<SpecialDataType1> + IsNot<SpecialDataType2>{ // ... }
Specialization using the Specialize
trait
If the data type for which you want to syllogism is a type parameter,
you cannot use the IsNot
trait because the compiler cannot know if
a type (even not in a dependent crate) will implement IsNot<Self>
.
Not implementing IsNot<Self>
is just a convention,
it is not compiler-enforced and the compiler cannot see
this. To work around this, you can use the Specialize
trait:
use syllogism::{Specialize, Distinction}; trait GenericTrait<T> { fn some_method(&self, param: T); } struct MyStruct<T> { // ... } struct SpecialDataType { /* ... */ } impl Specialize<SpecialDataType> for SpecialDataType { fn specialize(self) -> Distinction<SpecialDataType, Self> { Distinction::Special(self) } } struct GenericDataType1 { /* ... */ } impl Specialize<SpecialDataType> for GenericDataType1 { fn specialize(self) -> Distinction<SpecialDataType, Self> { Distinction::Generic(self) } } struct GenericDataType2 { /* ... */ } impl Specialize<SpecialDataType> for GenericDataType2 { // Similar to the implementation of Specialize for `GenericDataType1`. } // Can be used for each `T` in `SpecialDataType`, `GenericDataType1` and `GenericDataType2`. impl<T> GenericTrait<T> for MyStruct<T> where T: Specialize<SpecialDataType> { fn some_method(&self, param: T) { match param.specialize() { Distinction::Special(special) => { // The special implementation. }, Distinction::Generic(generic) => { // The generic implementation. } } } }
Using the Specialize
trait is limited to methods that take the parameter by value
(as oposed to by reference).
Implementing IsNot
and Serialize
across crate boundaries
As discussed before, for any data types T1
and T2
that you want to syllogism for
or that are used in the general case, you may want to implement
impl IsNot<T2> for T1 {}
(1) andimpl IsNot<T1> for T2 {}
(2).
Because of the orphan rules, (1) can only be implemented in the crate that defines T1
and
(2) can only be implemented in the crate that defines T2
.
In order to loosen the coupling and dependency between the crates, for each crate, a trait is
defined ("NotInCrateX
") and a blanket impl combines the crates together:
// In some crate pub trait NotInCrate1 {} pub trait NotInCrate2 {}
These macros are then imported in crate1
and crate2
and used as follows:
// In crate1 use syllogism::{IsNot}; struct T1 { // ... } impl<T> IsNot<T> for T1 where T: NotInCrate1 {} impl NotInCrate2 for T1 {}
and
// In crate2 use syllogism::{IsNot}; struct T2 { // ... } impl<T> IsNot<T> for T2 where T: NotInCrate2 {} impl NotInCrate1 for T2 {}
Now if there are many crates at play, you need more trait impls:
// In crate1: use syllogism::{IsNot}; struct T1 { // ... } impl<T> IsNot<T> for T1 where T: NotInCrate1 {} impl NotInCrate2 for T1 {} impl NotInCrate3 for T1 {} // ...
In order to help you with these impls, you can use the define_compatibility
macro
to define the NotInCrateX
traits and to automatically generate macros for implementing
NotInCrate2
, NotInCrate3
, ... .
Macros
define_compatibility | A macro to define traits and other macros that help to implement |
Enums
Distinction | An enum that allows to distinguish between the special case and the generic case. |
Traits
IsNot | A trait to inform the compiler that two data types are distinct. |
Specialize | A trait allowing to distinguish between the special case and the generic case at run time. |