Extension Attributes
Extension attributes let your crate define custom #[facet(...)] attributes with compile-time validation and helpful error messages.
For the full guide on creating extension attributes, see this page; for a quick consumer reference, see Extension Attributes.
Using extension attributes
use facet:: Facet ;
use facet_kdl as kdl;
# [ derive ( Facet )]
struct Server {
# [ facet ( kdl :: argument )]
name : String ,
# [ facet ( kdl :: property )]
host : String ,
}
The namespace (kdl) comes from how you import the crate:
use facet_kdl as kdl; // Enables kdl:: prefix
use facet_args as args; // Enables args:: prefix
Declaring attributes with define_attr_grammar!
Use the define_attr_grammar! macro to declare your attribute grammar. Here's how facet-kdl does it:
facet:: define_attr_grammar! {
ns "kdl" ;
crate_path :: facet_kdl;
/// KDL attribute types for field and container configuration.
pub enum Attr {
/// Marks a field as a single KDL child node
Child ,
/// Marks a field as collecting multiple KDL children
Children ,
/// Marks a field as a KDL property (key=value)
Property ,
/// Marks a field as a single KDL positional argument
Argument ,
/// Marks a field as collecting all KDL positional arguments
Arguments ,
/// Marks a field as storing the KDL node name
NodeName ,
}
}
This generates:
- An
Attrenum with variants for each attribute - Compile-time parsing that validates attribute usage
- Type-safe data storage accessible at runtime
Grammar components
| Component | Purpose | Example |
|---|---|---|
ns "..."; | Namespace for attributes | ns "kdl"; → #[facet(kdl::child)] |
crate_path ...; | Path to your crate for macro hygiene | crate_path ::facet_kdl; |
pub enum Attr { ... } | The attribute variants | See above |
Variant types
Unit variants (markers)
Simple flags with no arguments:
pub enum Attr {
/// A marker attribute
Child ,
}
Usage: #[facet(kdl::child)]
String values
Attributes that take a string:
pub enum Attr {
/// Rename to a different name
Rename ( &' static str ),
}
Usage: #[facet(rename = "new_name")]
Optional characters
For single-character flags (like CLI short options):
pub enum Attr {
/// Short flag, optionally with a character
Short ( Option < char >),
}
Usage: #[facet(args::short)] or #[facet(args::short = 'v')]
Advanced: how built-in attributes work
The built-in facet attributes use additional payload types not typically needed by extension crates. For reference:
// Inside the facet crate itself:
define_attr_grammar! {
builtin;
ns "" ;
crate_path :: facet :: builtin ;
pub enum Attr {
// Simple markers
Sensitive ,
Skip ,
Flatten ,
// String values
Rename ( &' static str ),
Tag ( &' static str ),
// Function-based defaults (uses field type's Default impl)
Default ( make_t or $ty :: default ()),
// Predicate functions for conditional serialization
SkipSerializingIf ( predicate SkipSerializingIfFn ),
// Type references (for proxy serialization)
Proxy ( shape_type ),
}
}
These special payload types enable powerful features but are primarily for core facet development.
Compile-Time validation
One of the major benefits of define_attr_grammar!: typos are caught at compile time with helpful suggestions.
# [ derive ( Facet )]
struct Parent {
# [ facet ( kdl :: chld )] // Typo!
child : Child ,
}
error: unknown attribute `chld`, did you mean `child`?
available attributes: child, children, property, argument, arguments, node_name
--> src/lib.rs:4:12
|
4 | #[facet(kdl::chld)]
| ^^^^^^^^^
The system uses string similarity to suggest corrections.
Querying attributes at runtime
When your format crate needs to check for attributes, use the get_as method on ExtensionAttr:
use facet_core::{ Field , FieldAttribute , Facet };
use facet_kdl:: Attr as KdlAttr ;
fn process_field ( field : & Field ) {
for attr in field. attributes {
if let FieldAttribute :: Extension ( ext) = attr {
// Check namespace first
if ext. ns == Some ( "kdl" ) {
// Get typed attribute data
if let Some ( kdl_attr) = ext. get_as ::< KdlAttr >() {
match kdl_attr {
KdlAttr :: Child => { /* handle child */ }
KdlAttr :: Property => { /* handle property */ }
KdlAttr :: Argument => { /* handle argument */ }
// ...
}
}
}
}
}
}
For built-in attributes:
use facet:: builtin:: Attr as BuiltinAttr ;
for attr in field. attributes {
if let FieldAttribute :: Extension ( ext) = attr {
if ext. is_builtin () {
if let Some ( builtin) = ext. get_as ::< BuiltinAttr >() {
match builtin {
BuiltinAttr :: Rename ( name) => { /* use renamed field */ }
BuiltinAttr :: Skip => { /* skip this field */ }
// ...
}
}
}
}
}
Namespacing
- Use short aliases if desired:
use facet_kdl as k; #[facet(k::child)]. - Namespaces prevent collisions across format crates.
- Built-in attributes remain short (
#[facet(rename = "...")], etc.).
Real-World examples
facet-args
facet-args provides CLI argument parsing:
facet:: define_attr_grammar! {
ns "args" ;
crate_path :: facet_args;
pub enum Attr {
/// Marks a field as a positional argument
Positional ,
/// Marks a field as a named argument
Named ,
/// Short flag character
Short ( Option < char >),
/// Marks a field as a subcommand
Subcommand ,
}
}
Usage:
use facet_args as args;
# [ derive ( Facet )]
struct Cli {
# [ facet ( args :: named , args :: short = 'v' )]
verbose : bool ,
# [ facet ( args :: positional )]
input : String ,
# [ facet ( args :: subcommand )]
command : Command ,
}
facet-yaml (serde compatibility)
facet-yaml provides serde-compatible names:
pub mod serde {
facet:: define_attr_grammar! {
ns "serde" ;
crate_path :: facet_yaml:: serde;
pub enum Attr {
/// Rename a field
Rename ( &' static str ),
}
}
}
Usage:
use facet_yaml:: serde;
# [ derive ( Facet )]
struct Config {
# [ facet ( serde :: rename = "serverName" )]
server_name : String ,
}
Next steps
- Learn what information
Shapeexposes: Shape. - See how to read values: Peek.
- Build values (strict vs deferred): Partial.
- Put it together for a format crate: Build a Format Crate.