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:

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(());
}