# facet-rs/facet: runtime reflection for Rust without a derive per format

> facet gives Rust types a SHAPE constant describing kind, layout, fields and attributes, and builds serialization on top of it. The ecosystem is broad but still at 0.50.0-rc, and the README itself warns that crates are in various states of progress.

**facet-rs/facet** — Rust reflection, serialization, deserialization, pretty printing, etc. — the last proc macro you should need

- Repository: https://github.com/facet-rs/facet
- Website: https://facet.rs
- Stars: 2,585 · Forks: 133
- Language: Rust
- License: Apache-2.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/facet-rs-facet

## The problem facet targets: one reflection layer instead of one derive per format

In Rust, a type that needs to travel across formats usually carries a derive per format. Add serde and you get JSON, TOML and YAML from the same attributes, which is the common answer. facet takes a different route: the type itself exposes a SHAPE associated constant, and format crates read that shape instead of requiring their own derive. The README describes the core crates as giving types a SHAPE constant "with their kind, layout, fields, documentation, attributes, and type-specific operations." That is a reflection model, not a serialization model. It is aimed at people who want the description of a type to exist at runtime as data: schema generation, structural diffing, generic pretty printing, or a tool that walks an arbitrary value it has never seen. If you only ever serialize one struct to JSON, this is more machinery than the job needs.

## How SHAPE, facet-reflect and the format crates fit together

The workspace splits the work. facet-core defines the Facet trait, the Shape struct, the Def tree, and type-erased pointer helpers named PtrUninit, PtrConst and Opaque. facet-macros implements the Facet derive, described in the README as "a fast/light proc macro powered by unsynn". A separate crate, facet-reflect, is where values get built and read: the README says it "allows building values of arbitrary shapes in safe code, respecting invariants. It also allows peeking at existing values." That invariant-preserving build step is the interesting part. Reflection that can construct values has to know when a partially initialized struct is still invalid, and facet-reflect is the layer that handles it. Everything else is downstream. facet-json, facet-toml, facet-yaml, facet-msgpack, facet-asn1, facet-xdr, facet-csv and facet-xml consume shapes. Utilities sit alongside: facet-pretty for printing, facet-diff for diffing, facet-assert for assertions that need no PartialEq, facet-value for a dynamic value type, and facet-serialize and facet-deserialize for generic iterative conversion. The dependency direction is one-way: formats depend on reflection, not the reverse.

## Getting facet into a project and deriving a first shape

The README does not include an install command, so there is nothing to copy verbatim here; it points at docs.rs/facet for documentation, and the main facet crate re-exports symbols from facet-core and facet-macros, which makes a single dependency the intended entry point. Add facet to your Cargo.toml dependencies and derive Facet on a type. The derive is what produces the SHAPE constant, and the README describes it as "a fast/light proc macro powered by unsynn". Once a type carries the derive, the format crates in the ecosystem read its shape rather than requiring a format-specific implementation. If you want to inspect or construct values rather than only convert them, the README points at facet-reflect for building values of arbitrary shapes in safe code and peeking at existing ones. Watch the version: the workspace manifest declares version 0.50.0-rc.5 for the package and 0.50.0-rc.7 for the facet, facet-core, facet-reflect and related dependencies, so expect to pin a release candidate rather than a stable 1.x.

## Where facet is the wrong tool

The README is unusually direct about this: "Crates are in various states of progress, buyer beware!" A 0.50.0-rc version line means API churn is expected, and the recent releases listed are release candidates (rusqlite-facet-v2.0.0-rc.6, rediff-v0.50.0-rc.6, facet-zod-v0.50.0-rc.6, all dated 2026-08-20). There is a second warning that matters more for evaluation: the README states the facet.rs website "has a lot of information about a lot of the ecosystem but it's unfortunately not super reliable as LLMs have been doing too much of the writing" and that it is being repaired. So the website is not a trustworthy source for API details right now. Read docs.rs and the repository instead. Beyond stability, the design itself is a trade-off. Runtime reflection means the type description is data at runtime, which costs binary size and compile time compared with a derive that expands to direct field access. If your workload is a hot serialization path and you already have serde, moving to facet buys you generality you may not use. The README also notes that several related projects have moved out to their own repositories, including Figue for CLI and configuration loading and Styx for typed configuration documents, so anything you saw under the facet umbrella may now live elsewhere.

## facet compared with serde

serde is the obvious alternative and the difference is architectural. serde's Serialize and Deserialize traits are implemented per type, usually by derive, and each format crate drives those traits. The type knows how to serialize itself. facet inverts that: the type exposes a shape, and the format crate decides what to do with it. The practical consequence is that a new format does not require touching your types, and a tool that only has a Shape can walk or construct a value it was never compiled against. The cost is that the reflection data has to exist at runtime. serde also has years of format coverage and a stable 1.x line; facet is at 0.50.0-rc with its own format crates listed as being in various states of progress. If you need a battle-tested serialization stack today, serde is the safer default. If you are building schema generation, diffing, or a generic inspector, the shape-based model is the reason to look at facet at all.

## Maintenance, licensing and what to verify before adopting

The repository is not archived, and the last push was on 2026-09-25, three days before this writing, so development is current. The workspace declares license = "MIT OR Apache-2.0" and ships both LICENSE-MIT and LICENSE-APACHE at the top level; the README badge also shows MIT/Apache-2.0. That dual licence is the same permissive arrangement serde uses, which keeps it compatible with most Rust dependency trees, but the choice between the two licences is yours to make and this is not legal advice. Upgrade cost is the real question. The workspace version is 0.50.0-rc.5 while the internal dependencies are pinned at 0.50.0-rc.7, and release-plz.toml plus a renovate.json5 in the repository root suggest automated release and dependency tooling. A pre-1.0 line with release candidates means you should expect to read changelogs between upgrades; CHANGELOG.md is at the repository root. The Dockerfile shows the build environment expects rust:1.96-slim-bookworm by default and installs cargo-nextest, cargo-llvm-cov and cargo-hack, which tells you the project tests across feature combinations and tracks coverage, but that is about their CI, not about the API stability you will inherit.

## Conclusion

Adopt facet when you want one reflection layer feeding many formats, or when you need to build and mutate values generically through facet-reflect. Do not adopt it for a single JSON endpoint where serde already works, and do not expect a stable API: the workspace version is 0.50.0-rc.5 and the README says crates are in various states of progress. Before committing, check the crate you actually need on docs.rs, confirm its published version matches the 0.50.0-rc line, and read the facet.rs note about the website being unreliable.

## FAQ

### What is facet-rs/facet in Rust?

It is an ecosystem of Rust crates built on runtime reflection. The core crates give types a SHAPE associated constant holding kind, layout, fields, documentation, attributes and type-specific operations, and format crates such as facet-json and facet-toml read that shape.

### How do I install facet?

The README gives no install command; it points at docs.rs/facet for documentation. The main facet crate re-exports symbols from facet-core and facet-macros, so adding facet as a dependency and deriving Facet on your types is the route the README describes.

### Is facet a replacement for serde?

It overlaps but differs in approach: serde implements Serialize and Deserialize per type, while facet exposes a runtime shape that format crates read. The README lists JSON, TOML, YAML, MessagePack, ASN.1, XDR, CSV and XML crates, and warns that crates are in various states of progress.

### What does facet-reflect do?

According to the README, facet-reflect allows building values of arbitrary shapes in safe code while respecting invariants, and also allows peeking at existing values. It is the layer that turns a Shape into an actual constructed or inspected value.

### Is facet production ready?

The workspace version is 0.50.0-rc.5 with internal dependencies pinned at 0.50.0-rc.7, and the README says crates are in various states of progress. The repository itself is not archived and the last push was on 2026-09-25, so work is ongoing, but the version line is pre-1.0.

## Sources

- [facet-rs/facet on GitHub](https://github.com/facet-rs/facet)
- [License: Apache-2.0](https://github.com/facet-rs/facet/blob/main/LICENSE)
- [Project website](https://facet.rs)
- [README](https://github.com/facet-rs/facet/blob/main/README.md)
- [Releases](https://github.com/facet-rs/facet/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/facet-rs-facet
