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.