facet is a derive macro and a trait that gives runtime (and to some extent, const-time) knowledge about the shape, trait implementations, and characteristics of arbitrary types.

If you're coming from serde and just want to see the differences, check out the Serde Comparison.

It can serve a lot of use cases typically handled by proc macros, like:

  • Pretty-printing
  • Run-time introspection
  • Debugging (incl. mutating values)
  • Serializing, and deserializing
  • Code generation (via build scripts)
  • Diffing values

Note: This documentation is being restructured. If it feels a bit scattered, that's because it is! We're working on it.

Crash course

You derive it like Serialize or Deserialize except there's only one macro:

#[derive(Facet)]
struct FooBar {
    foo: u32,
    bar: String,
}

Now, FooBar::SHAPE, of type Shape, lets us know:

  • Whether it's a struct, an enum, a list, a map, an array, a slice, a scalar (see Def)
  • What fields it has (if it's a struct, an enum, a tuple, etc.)
    • Also, which offset they're at and their shape, of course
  • What variants it has if it's an enum...

But also:

This takes into account type parameters (which you can also inspect at runtime), so:

  • For example, <Vec<i32>>::SHAPE.vtable.debug is Some(_)
  • For example, <Vec<MyNonDebugStruct>>::SHAPE.vtable.debug is None(_)

Reflection

However, vtables are low-level and unsafe, and you would normally invoke stuff through facet-reflect types like:

  • Peek when reading from a value
  • Partial when building values from scratch

These two abstractions are used by serializers and deserializers respectively, and are fully safe, despite dealing with partially-initialized values under the hood.

What can you build with it?

The Facet trait lends itself to a surprisingly large number of use cases.

A better Debug

You can replace Debug with facet-pretty and get:

  • Nice colors via owo-colors (even in no-std)
  • Un-printable fields will just have their types printed
  • Sensitive fields will be redacted

A better assert!

Crates like pretty-assertions make a diff of the Debug representation of two types.

Wouldn't it be better to have access to the whole type information of both sides and do a structural difference, knowing the affinity of every scalar, having access to display implementations, but not just, something more like difftastic. than diff?

A more flexible serde

You can use facet-json, facet-toml and others to serialize and deserialize data.

Those are bound to be slower than serde, which generates optimized code. So why bother?

Well, serde generates a lot of code. And it depends on heavy packages like syn.

Cold build times (and often, hot build times) suffer, in the presence of a lot of large data structures. If runtime performance is not the bottleneck, facet can help by:

  • Deriving data, not code
  • Avoiding combinatorial explosion due to monomorphization

What does that last point mean? serde generates different code for Vec<T>, Vec<U>, Vec<W>, etc.

What's more, it generates different code (via generics, too) for every serializer and deserializer. This may be very efficient at runtime, but it makes some projects' compile time very, very long.

With facet, serialization and deserialization is implemented:

  • Once per type (Vec<T> for any T)
  • Once per data format (JSON, TOML, etc.)

You can have mycrate-types crates, with every struct deriving Facet, with no worries. No need to put it behind a feature flag even, the main facet crate is relatively light, thanks to its use of the lightweight unsynn instead of syn.

facet has a lot more information about your types than serde does, which means it's able to generate better errors, and decide things about deserialization that can't really be done with serde without breaking its interface, like:

  • Deciding at runtime what to do about duplicate fields
  • Deciding at runtime what to fill a missing field with?
  • Only deserializing part of the data, with JSONPath-like selectors

Additionally, deserializers like facet-json's are designed to be iterative, not recursive. You can deserialize very very deep data structures without blowing up the stack. As long as you got enough heap, you're good to go.

Code generation

If you don't mind building your types crates as a build dependency, too, you could then use reflection to generate Rust code and thus reach serde-level speeds, if you generate serialization/deserialization code, for example.

Specialization (at runtime)

We're not talking about compiling different code based on the T in Vec<T> — however, you can reflect on the T (if you're comfortable adding a T: Facet bound) and dynamically call methods on it.

For example, facet-pretty prints Vec<u8> different than other Vec types.

Better debuggers

See this issue for an interesting discussion.

Diffing?

See this issue for talk about diffing

Better support for XML/KDL

Those don't fit the serde data model so well. More discussion over at:

Other data formats (protobuf? postcard?) would also probably benefit from additional attributes.

Better JSON schemas

facet gives you access to doc comments, so JSON-schemas generated from that information could in theory be more complete than those from, say, serde.

Derive Error

Like displaydoc but without the added syn (see free of syn)?

Much, much more

We still haven't figured everything facet can do. Come do research with us:

See also: Comparison with Serde for a side-by-side look at derive macro attributes.

Contributing

Contributions are welcome! Check out the GitHub repository to get started.