Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Elements

Elements are reusable template components. Define them with #[zyn::element] and invoke them with @ in templates.

Defining

Annotate a function with #[zyn::element]. Parameters become struct fields (props); the function must return zyn::TokenStream:

#[zyn::element]
fn field_decl(vis: syn::Visibility, name: syn::Ident, ty: syn::Type) -> zyn::TokenStream {
    zyn::zyn! {
        {{ vis }} {{ name }}: {{ ty }},
    }
}

The macro generates a struct and a Render impl:

pub struct FieldDecl {
    pub vis: syn::Visibility,
    pub name: syn::Ident,
    pub ty: syn::Type,
}

impl zyn::Render for FieldDecl {
    fn render(&self, input: &zyn::Input) -> zyn::TokenStream {
        let vis = &self.vis;
        let name = &self.name;
        let ty = &self.ty;
        zyn::zyn! { {{ vis }} {{ name }}: {{ ty }}, }
    }
}

The function name (snake_case) becomes the template directive name. The struct name is the PascalCase equivalent — field_declFieldDecl.

Elements are always infallible. For error handling, see Diagnostics.

Invoking

Reference an element by its snake_case name prefixed with @. Props are passed as name = value pairs:

zyn! {
    @field_decl(
        vis = field.vis.clone(),
        name = field.ident.clone().unwrap(),
        ty = field.ty.clone(),
    )
}
// output: pub age: u32,

Prop values are raw Rust expressions — any expression that produces the right type works. Trailing commas are allowed. Elements can be invoked as many times as needed:

zyn! {
    @for (field in fields.iter()) {
        @field_decl(
            vis = field.vis.clone(),
            name = field.ident.clone().unwrap(),
            ty = field.ty.clone(),
        )
    }
}

The input Parameter

Every element’s render body has an input: &zyn::Input in scope. This is the proc macro input context — the item being annotated:

#[zyn::element]
fn my_element(name: syn::Ident) -> zyn::TokenStream {
    let ident = input.ident();
    zyn::zyn! { /* ... */ }
}

When using #[zyn::derive] or #[zyn::attribute], input is provided automatically. For manual usage, define let input: zyn::Input = ...; before calling zyn!:

let input: zyn::Input = real_derive_input.into();
zyn::zyn! {
    @my_element(name = ident)
}

See Proc Macro Entry Points for the recommended approach.

Extractor Params

Parameters marked with #[zyn(input)] are automatically resolved from the input context — they are not props and are not passed at the call site:

#[zyn::element]
fn my_element(
    #[zyn(input)] cfg: zyn::Attr<MyConfig>,
    #[zyn(input)] fields: zyn::Fields,
    label: syn::Ident,
) -> zyn::TokenStream {
    zyn::zyn! { /* cfg, fields, label all available */ }
}

Call site — only props are passed:

@my_element(label = some_ident)

See Extractors for the full list of extractor types.