Expand description
Generates idiomatic source code in Swift, Kotlin, TypeScript, and C# from Rust types
annotated with #[derive(Facet)].
When Rust is the source of truth for a data model shared across platforms (e.g. a mobile app talking to a Rust core over FFI), every target language needs matching type definitions — and, optionally, serialization code to move data across the boundary. Writing these by hand is tedious and error-prone; this crate automates it.
Optionally, when a plugin such as BincodePlugin or
JsonPlugin is configured, the generated types include
serialize / deserialize methods and the appropriate runtime library is installed
alongside the generated code.
§Modules
reflection— walks Rust type metadata (via thefacetcrate) and builds a language-neutralRegistry: a flat map from qualified type names to theirContainerFormatdescriptions.generation— transforms a registry into source code for a target language. Each language (kotlin,csharp,swift,typescript) lives behind a feature flag and follows a three-layer pipeline: Installer (project scaffolding and manifests) → Generator (file-level output with imports and namespaces) → Emitter (per-type code emission).
§Getting Started
Add the crates to your project:
cargo add facet facet_generate§1. Annotate your types
Derive facet::Facet on every type you want to share across language boundaries.
Aliasing this crate as fg keeps attribute paths short:
use facet::Facet;
use facet_generate as fg;
#[derive(Facet)]
#[repr(C)]
enum HttpResult {
Ok(HttpResponse),
Err(HttpError),
}
#[derive(Facet)]
struct HttpResponse {
status: u16,
headers: Vec<HttpHeader>,
#[facet(fg::bytes)] // Vec<u8> → native byte-array type
body: Vec<u8>,
}
#[derive(Facet)]
struct HttpHeader {
name: String,
value: String,
}You only need to register root types — all referenced types are collected transitively.
§2. Build a Registry
use facet_generate::reflection::RegistryBuilder;
let registry = RegistryBuilder::new()
.add_type::<HttpResult>()?
.build()?;§3. Generate code
Pass the Registry to a language-specific installer, optionally add plugins for
serialization support, and call generate():
use facet_generate::generation::{bincode::BincodePlugin, kotlin, swift, typescript};
// Swift package with Bincode serialization
swift::Installer::new("MyPackage", &out_dir)
.plugin(BincodePlugin)
.generate(®istry)?;
// Kotlin package with Bincode serialization
kotlin::Installer::new("com.example", &out_dir)
.plugin(BincodePlugin)
.generate(®istry)?;
// TypeScript with Bincode serialization
typescript::Installer::new("my-package", &out_dir)
.plugin(BincodePlugin)
.generate(®istry)?;Each installer writes a ready-to-build project to out_dir — type definitions plus,
when a serialization plugin is configured, the appropriate runtime. Omit
.plugin(...) to generate plain type definitions without any serialization code.
§Key attributes
| Attribute | Effect |
|---|---|
#[facet(fg::bytes)] | Emit Vec<u8> / &[u8] as a native byte-array type ([UInt8], ByteArray, Uint8Array) |
#[facet(fg::namespace = "ns")] | Group a type, transitively, into a named namespace, emitted as a separate module |
#[facet(fg::namespace)] | Group a type, transitively, into the ROOT namespace |
#[facet(rename = "Name")] | Override the generated name of a type, field, or variant |
#[facet(rename_all = "camelCase")] | Apply a naming convention across all fields or variants. Options are PascalCase, camelCase, snake_case, SCREAMING_SNAKE_CASE, kebab-case, SCREAMING-KEBAB-CASE |
#[facet(skip)] | Exclude a field or variant from the generated output |
#[facet(opaque)] | Do not descend into the field’s type |
#[facet(transparent)] | Unwrap a newtype wrapper in the generated output |
§Testing
Tests are organized in four layers, from fast and narrow to slow and broad:
§Unit tests (snapshot)
Each language has snapshot-based tests that assert on generated text without touching the filesystem.
| Layer | Location | What it covers |
|---|---|---|
| Emitter | generation/<lang>/emitter/tests.rs (+ tests_bincode.rs, tests_json.rs) | Output for individual types — no file headers, no imports. Uses the emit macro. |
| Generator | generation/<lang>/generator/tests.rs | Full file output including package declarations, imports, and namespace-qualified names. |
| Installer | generation/<lang>/installer/tests.rs | Generated manifest strings (.csproj, build.gradle.kts, package.json, Package.swift). Still pure string assertions — no files written. |
All three use the insta crate for snapshot assertions.
§Cross-language expect-file tests (tests module, src/tests/)
Each sub-module defines one or more Rust types and invokes the test! macro, which reflects
the types and runs the full CodeGenerator pipeline for every listed language
(e.g. for kotlin, swift). The output is compared against checked-in expect files
(output.kt, output.swift, …) sitting alongside each mod.rs, using the
expect_test crate. These tests are fast (no compiler
invocation) but exercise the complete generator path — including package declarations, imports,
and multi-type ordering — across multiple languages in a single test case. Every test should
support all languages, except for a few that exercise language-specific features like
#[facet(swift = "Equatable")] or #[facet(kotlin = "Parcelable")].
Gated on #[cfg(all(test, feature = "generate"))].
§Compilation tests (tests/<lang>_generation.rs)
Integration tests that generate code and a project scaffold into a temporary directory,
then invoke the real compiler (dotnet build, gradle build, swift build, tsc).
They verify that the generated code is syntactically and type-correct in the target language.
Each file is feature-gated (e.g. #![cfg(feature = "kotlin")]) so tests only run when the
corresponding toolchain is available.
§Runtime tests (tests/<lang>_runtime.rs)
End-to-end tests that go one step further: they serialize sample data in Rust (typically with bincode), generate target-language code that deserializes the same bytes, compile and run the resulting program, and assert that the round-trip is correct. These catch subtle encoding bugs that snapshot and compilation tests cannot.
Modules§
- error
- generation
- Code generation — transforms a
Registryinto source code. - reflection
- Type reflection engine — extracts Rust type metadata into a language-neutral
Registry.
Macros§
Enums§
- Attr
- Extension attributes for facet_generate code generation.