Ecosystem Integration

Facet provides Facet trait implementations for many popular Rust crates via feature flags. Enable the feature, and those types work seamlessly with all facet format crates.

Third-Party type support

Enable these features in your Cargo.toml:

[dependencies]
facet = { version = "{{ data.versions.facet }}", features = ["uuid", "chrono"] }

Available features

FeatureCrateTypes
uuiduuidUuid
ulidulidUlid
urlurlUrl
chronochronoDateTime<Tz>, NaiveDate, NaiveTime, NaiveDateTime
timetimeDate, Time, PrimitiveDateTime, OffsetDateTime, Duration
jiff02jiffTimestamp, Zoned, DateTime, Date, Time, Span, SignedDuration
caminocaminoUtf8Path, Utf8PathBuf
bytesbytesBytes, BytesMut
ordered-floatordered-floatOrderedFloat<f32>, OrderedFloat<f64>, NotNan<f32>, NotNan<f64>
ruintruintUint<BITS, LIMBS>, Bits<BITS, LIMBS>

Example: uUIDs

use facet::Facet;
use uuid::Uuid;

#[derive(Facet)]
struct User {
    id: Uuid,
    name: String,
}

let json = r#"{"id": "550e8400-e29b-41d4-a716-446655440000", "name": "Alice"}"#;
let user: User = facet_json::from_str(json)?;

Example: dateTime with chrono

use facet::Facet;
use chrono::{DateTime, Utc};

#[derive(Facet)]
struct Event {
    name: String,
    timestamp: DateTime<Utc>,
}

let json = r#"{"name": "deploy", "timestamp": "2024-01-15T10:30:00Z"}"#;
let event: Event = facet_json::from_str(json)?;

Example: UTF-8 paths with camino

use facet::Facet;
use camino::Utf8PathBuf;

#[derive(Facet)]
struct Config {
    data_dir: Utf8PathBuf,
}

Extended tuple support

By default, facet supports tuples up to 4 elements. Enable tuples-12 for tuples up to 12 elements:

[dependencies]
facet = { version = "{{ data.versions.facet }}", features = ["tuples-12"] }

Function pointer support

Enable fn-ptr for Facet implementations on function pointer types:

[dependencies]
facet = { version = "{{ data.versions.facet }}", features = ["fn-ptr"] }

Standard library type support

Some standard library types require feature flags:

FeatureTypes
nonzeroNonZero<T> types (NonZeroU8, NonZeroI32, etc.)
netSocketAddr, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6
[dependencies]
facet = { version = "{{ data.versions.facet }}", features = ["nonzero", "net"] }

Doc comment extraction

By default, doc comments (/// ...) are not included in generated Shape, Field, and Variant definitions to reduce compile times and binary size. Enable the doc feature to include them:

[dependencies]
facet = { version = "{{ data.versions.facet }}", features = ["doc"] }

This is useful for:

  • facet-args: Including doc comments in CLI help text generation
  • facet-pretty: Showing doc comments in pretty-printed output
  • Custom tooling: Building documentation generators or IDE integrations

Without this feature, .doc fields will be empty slices (&[]).

Typo suggestions in derive errors

By default, when you mistype an attribute name in #[facet(...)], the derive macro suggests corrections using string similarity matching. This requires the strsim dependency.

To disable this and reduce compile times slightly, turn off the helpful-derive feature:

[dependencies]
facet = { version = "{{ data.versions.facet }}", default-features = false, features = ["std"] }

With helpful-derive enabled (default):

error: unknown attribute `renam`, did you mean `rename`?

Without it:

error: unknown attribute `renam`; expected one of: rename, skip, default, ...

facet-args: CLI argument parsing

Beyond basic argument parsing, facet-args provides utilities for help generation and shell completions.

Help generation

Generate formatted help text from your type's structure and doc comments:

use facet::Facet;
use facet_args::{generate_help, HelpConfig};

/// A file processing tool.
#[derive(Facet)]
struct Args {
    /// Enable verbose output
    #[facet(args::named, args::short)]
    verbose: bool,

    /// Input file to process
    #[facet(args::positional)]
    input: String,
}

fn main() {
    let config = HelpConfig {
        program_name: Some("mytool".into()),
        version: Some("1.0.0".into()),
        ..Default::default()
    };

    println!("{}", generate_help::<Args>(&config));
}

Shell completions

Generate completion scripts for bash, zsh, and fish:

use facet_args::{generate_completions, Shell};

// Generate bash completions
let bash = generate_completions::<Args>(Shell::Bash, "mytool");
println!("{}", bash);

// Generate zsh completions
let zsh = generate_completions::<Args>(Shell::Zsh, "mytool");

// Generate fish completions
let fish = generate_completions::<Args>(Shell::Fish, "mytool");

Install completions by writing to the appropriate location:

  • Bash: ~/.local/share/bash-completion/completions/mytool
  • Zsh: ~/.zsh/completions/_mytool
  • Fish: ~/.config/fish/completions/mytool.fish

Parsing from std::env

For quick CLI tools:

use facet_args::from_std_args;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args: Args = from_std_args()?;
    // ...
    Ok(())
}

See the Args showcase for comprehensive examples including subcommands, error messages, and more.

When a type doesn't implement Facet

If you have a type that doesn't implement Facet, you have several options:

Your own type

If it's your type, just derive it:

#[derive(Facet)]
struct MyType {
    // ...
}

Type with non-Facet fields

If your type contains fields that don't implement Facet, use opaque to hide them:

use some_crate::ExternalType;  // Doesn't implement Facet

#[derive(Facet)]
struct MyWrapper {
    name: String,
    #[facet(opaque)]
    internal: ExternalType,  // Hidden from serialization
}

Opaque fields can't be serialized on their own. If you need serialization, add a proxy:

#[derive(Facet)]
#[facet(transparent)]
struct ExternalTypeProxy(String);

impl TryFrom<ExternalTypeProxy> for ExternalType {
    type Error = &'static str;
    fn try_from(proxy: ExternalTypeProxy) -> Result<Self, Self::Error> {
        ExternalType::parse(&proxy.0).ok_or("invalid format")
    }
}

impl TryFrom<&ExternalType> for ExternalTypeProxy {
    type Error = std::convert::Infallible;
    fn try_from(val: &ExternalType) -> Result<Self, Self::Error> {
        Ok(ExternalTypeProxy(val.to_string()))
    }
}

#[derive(Facet)]
struct MyWrapper {
    name: String,
    #[facet(opaque, proxy = ExternalTypeProxy)]
    internal: ExternalType,
}

// Serialization: ExternalType → proxy → JSON string
let wrapper = MyWrapper {
    name: "example".into(),
    internal: ExternalType::new(),
};
let json = facet_json::to_string(&wrapper);
// {"name":"example","internal":"...serialized form..."}

// Deserialization: JSON → proxy → ExternalType
let parsed: MyWrapper = facet_json::from_str(&json).unwrap();

See the Attributes Reference for details on opaque and proxy.

Third-party type you want full support for

If you want a third-party type to work seamlessly with facet (like uuid::Uuid does), you can contribute an implementation to facet. See Implementing Facet for third-party types.

no_std support

Facet works in no_std environments. Disable default features and enable alloc:

[dependencies]
facet = { version = "{{ data.versions.facet }}", default-features = false, features = ["alloc"] }

Some format crates also support no_std:

  • facet-json — with alloc feature
  • facet-postcard — with alloc feature
  • facet-msgpack — with alloc feature