Implementing Facet for third-party types

This guide is for contributing Facet implementations to the facet repository. If you just want to use a type that doesn't implement Facet, see When a type doesn't implement Facet.

Why we implement from the facet side

In Rust, you can only implement a trait in one of two places:

  1. The crate that defines the trait
  2. The crate that defines the type

Ideally, crates like chrono or uuid would implement Facet for their types directly. But facet isn't stable yet — the Facet trait and Shape structure are still evolving.

So we implement Facet for third-party types from the facet side, using optional features in facet-core (re-exported through facet). When facet stabilizes, crate authors can implement Facet themselves, and we'll deprecate our implementations.

Adding support for a new crate

  1. Add the dependency to facet-core/Cargo.toml:

    [dependencies]
    my-crate = { version = "1.0", optional = true }
    
    [features]
    my-crate = ["dep:my-crate"]
    
  2. Create facet-core/src/impls_my_crate.rs

  3. Add to facet-core/src/lib.rs:

    #[cfg(feature = "my-crate")]
    mod impls_my_crate;
    
  4. Re-export the feature from facet/Cargo.toml:

    [features]
    my-crate = ["facet-core/my-crate"]
    

Implementing Facet

Most third-party types are scalars (atomic values like UUIDs, timestamps, paths):

unsafe impl Facet<'_> for my_crate::MyType {
    const SHAPE: &'static Shape = &Shape {
        id: Shape::id_of::<Self>(),
        layout: Shape::layout_of::<Self>(),
        vtable: value_vtable!(my_crate::MyType, |f, _opts| {
            write!(f, "MyType")
        }),
        type_identifier: "MyType",
        def: Def::Scalar,
        ty: Type::User(UserType::Opaque),
        type_params: &[],
        doc: &[],
        attributes: &[],
        type_tag: None,
        inner: None,
    };
}

Look at existing implementations in facet-core/src/impls_* for patterns:

  • impls_uuid.rs — simple scalar
  • impls_chrono.rs — multiple related types
  • impls_camino.rs — path types with borrowed variants
  • impls_bytes.rs — byte buffer types

Collection types

Collections need vtable functions for their operations (push, get, len, etc.):

unsafe impl<T: Facet<'static>> Facet<'_> for MyVec<T> {
    const SHAPE: &'static Shape = &Shape {
        id: Shape::id_of::<Self>(),
        layout: Shape::layout_of::<Self>(),
        vtable: value_vtable!(MyVec<T>, |f, opts| {
            write!(f, "MyVec<")?;
            (T::SHAPE.vtable.type_name)(f, opts)?;
            write!(f, ">")
        }),
        type_identifier: "MyVec",
        def: Def::List(ListDef {
            vtable: &ListVTable {
                init_empty: |target| { /* ... */ },
                push: |list, value| { /* ... */ },
                len: |list| { /* ... */ },
                get: |list, index| { /* ... */ },
            },
            item_shape: T::SHAPE,
        }),
        ty: Type::User(UserType::Opaque),
        type_params: &[TypeParam { name: "T", shape: T::SHAPE }],
        doc: &[],
        attributes: &[],
        type_tag: None,
        inner: None,
    };
}

Testing

Add tests in the same file or in facet-core/tests/. Make sure to test:

  • Round-trip through at least one format (JSON is easiest)
  • Edge cases for the type (empty values, max values, etc.)