facet

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.

More information is available on its github repository

It’s intended to be “the last proc macro / the last derive you’ll ever need”, since it can serve a lot of use cases typically handled by proc macros, like:

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:

But also:

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

Reflection

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

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

Cool bear

For example, facet-json has #[deny(unsafe_code)] — all “format crates” do.

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:

Example outputs:

Person { name: Alice, age: 30, address: Address { street: 123 Main St, city: Wonderland, country: Imagination, }, }

Also:

TestSecrets { normal_field: This is visible, sensitive_field: [REDACTED], }

A better assert!

Crates like pretty-assertions make a diff of the Debug representation of two types. Wouldn’t it be

A more flexible serde

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

Cool bear

Those two are the most maintained — but there are others, and help is wanted

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:

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:

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 late, thanks to its use of the lightweight unsynn instead of syn.

Cool bear

But don’t trust us, make your own measurements! Be aware facet is still a bit rough around the edges.

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:

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.

Cool bear

Nobody’s has done this yet, will you be the first?

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:

facet on  main [$] via 🦀 v1.86.0 cargo run --example vec_u8 Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s Running `target/debug/examples/vec_u8` Vec<u8> 29 a6 6e 6a 8d 7e c9 52 c9 1e 4d 83 bf f5 8d 60 69 73 1d d2 76 d0 c2 75 b5 a7 c6 f9 af 73 03 fc b0 65 b7 19 eb 87 d3 a3 a5 73 e1 52 da 07 06 f3 15 d6 c2 f0 b3 61 6d 1d 4d 8a 5f 1c 31 da 98 25 b1 00 94 8c 34 ac 1c c6 05 09 f7 19 76 0f 51 3a 3d 1c e8 d9 9a 03 3d 26 bb ed df cf 39 be d4 0d 93 ae 84 60 d5 f0 7c 4f 62 a7 22 eb 9a d3 4c 70 bb 08 b3 ea b6 0b d0 d4 f9 82 88 80 5b 28 41 5d
Cool bear

Whether that’s a good idea or not is a different question.

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 generating JSON-scheams

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:

How to support facet

@fasterthanlime aka Amos Wenger is the original author and principal maintainer of facet.

You can support them:

Watching their videos (which are often Rust-focused) is also a nice way to support them.

You can adopt one of the format crates for facet, you can experiment with it, you can tell your friends about it, you can tackle one of the many open issues!

Check out the GitHub repository and start playing with facet today!