How to achieve Compile Time reflection in next ten thousand years

facet does everything at runtime, and doesn't interact well with the rest of the type system.
The polar opposite to that would be a fully generic API, which does everything at compile time.
I have experimented with that here, and I ended up with something similar to soasis' work. It looks something like this:

struct Cons<A, B>(pub A, pub B);

trait Introspect {
    const NAME: &'static str;
}

trait Struct: Introspect {
    // either UnitShape, TupleShape, or NamedShape
    type Shape: StructShape; 
    // e.g. `Cons<Field0, Cons<Field1, Cons<..., ()>>>`,
    // where `Field0`, `Field1`, .. are dummy types generated by the derive macro 
    // and implement the `Field` trait.
    type Fields; 
}

trait Field {
    const NAME: Option<&'static str>;
    type Type: ?Sized;
    type Root: Introspect;

    fn try_get_ref(p: &Self::Root) -> Option<&Self::Type>;
    fn try_get_mut(p: &mut Self::Root) -> Option<&mut Self::Type>;
}

At the heart of this API is Cons, which represents a list of types without the need for variadic generics. It allows for recursive impls, e.g

impl PrintFields for () {}
impl<Head: Field, Tail: PrintFields> PrintFields for Cons<Head, Tail> {
  fn print_fields() {
    println!("{:?}", Head::NAME);
    Tail::print_fields();
  }
}

With just that (and a lot of atrocious code) it is already possible to replace serde's derive macros (without helper attributes), which I've done here.
That code exposes newtype structs ser::Reflect<T> and de::Reflect<T>, which implement serde::Serialize or serde::Deserialize, given that T: Introspect.
To fully replace the serde derive macros, users would still need to actually implement serde::Serialize and serde::Deserialize for their own types:

impl Serialize for MyStruct {
    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        ser::Reflect(self).serialize(s)
    }
}

Unlike a blanket impl<T: Introspect> Serialize for T impl (or the approach facet is pursuing), this allows custom implementations and plays nicely with the type system.

And while I haven't benchmarked anything, this does hold the promise of being zero-cost, unlike facet.


None of this is particularly ergonomic at the moment, but does seem like a promising approach to me. A better API and language features like variadic generics, sealed/closed traits and disjoint impls wrt. associated types would likely make this much more ergonomic.

1 Like