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:
- Pretty-printing
- Run-time introspection
- Debugging (incl. mutating values)
- Serializing, and deserializing
- Code generation (via build scripts)
- Diffing values
- And more!
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:
- Which traits are implemented for this shape
- via Characteristic
- and ValueVTable
This takes into account type parameters (which you can also inspect at runtime), so:
- For example,
<Vec<i32>>::SHAPE.vtable.debug
isSome(_)
- For example,
<Vec<MyNonDebugStruct>>::SHAPE.vtable.debug
isNone(_)
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.
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:
- Nice colors via owo-colors (even in no-std)
- Un-printable fields will just have their types printed
- Sensitive fields will be redacted
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.
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:
- 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 anyT
) - 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 late, thanks to its use
of the lightweight unsynn instead of syn
.
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:
- 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.
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
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:
- on Ko-fi
- on GitHub Sponsors
- on Patreon
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!