Workaround in Rust of having different implementations for types of different trait implementations
Yep, this is a quite complicated title, so let's illustrate our work in this article with a simple example, in Rust in a different world:
trait DataCap {
fn serialize(&self) -> String;
}
impl<T: Serialize> DataCap for T {
fn ser(&self) -> String {
Serializer::serialize(self, serializer)
}
}
impl<T: !Serialize> DataCap for T {
fn ser(&self) -> String {
unreachable!()
}
}
Sadly this is not accepted by Rust currently:
- Specialization is proven unsound, thus would never be stabilized and is not considered;
- The minimal version of specialization which requires the impls strictly subsetting doesn't apply to this case;
- Not to mention that negative bounds are far from being added to Rust;
- Turning to decide the impl during instantiation is against Rust's early error spirit and would never appear in Rust;
- The lean4 way which just gives up when conflicts and let users to explicit decide which impl should be used is good, but may be too far for industrial langs like Rust :)
Therefore our workaround is to mark whether the trait is implemented, and make a wrapper to unify the impls:
use std::marker::PhantomData;
trait Serialize {
fn serialize<S>(&self, s: S) -> String;
}
impl Serialize for i32 {
fn serialize<S>(&self, _s: S) -> String { todo!() }
}
pub trait SerFlag {}
pub struct WithSer;
pub struct NoSer;
impl SerFlag for WithSer {}
impl SerFlag for NoSer {}
// User should impl this
trait DataCap {
type HasSer: SerFlag;
}
trait DataSer {
fn serialize(&self) -> String;
}
struct Wrap<T, S: SerFlag>(T, PhantomData<S>);
impl<T: DataCap<HasSer = WithSer> + Serialize> DataSer for Wrap<T, WithSer> {
fn serialize(&self) -> String { self.0.serialize(()) }
}
impl<T: DataCap<HasSer = NoSer>> DataSer for Wrap<T, NoSer> {
fn serialize(&self) -> String { "".to_string() }
}
fn use_data<T: DataCap>(data: T) where Wrap<T, T::HasSer>: DataSer {
let data = Wrap(data, PhantomData::<T::HasSer>);
data.serialize();
}
impl DataCap for i32 {
type HasSer = WithSer;
}
impl DataCap for () {
type HasSer = NoSer;
}
fn main() {
use_data(0);
use_data(());
}