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

Advanced

Custom Names

By default the template name is derived from the function name. Pass a string to #[zyn::element] to override it:

#[zyn::element("say_hello")]
fn internal_greeting(name: syn::Ident) -> zyn::TokenStream {
    zyn::zyn!(fn {{ name }}() {})
}

zyn! { @say_hello(name = input.ident.clone()) }

The generated struct is still named SayHello (PascalCase of the custom name).

Custom names are useful when:

  • The natural Rust function name is verbose or internal: fn render_field_declaration@field
  • You want a domain-specific vocabulary: fn emit_getter_method@getter
  • The function name conflicts with a Rust keyword: fn type_decl@type_def
#[zyn::element("getter")]
fn emit_getter_method(name: syn::Ident, ty: syn::Type) -> zyn::TokenStream {
    zyn::zyn! {
        pub fn {{ name | ident:"get_{}" }}(&self) -> &{{ ty }} {
            &self.{{ name }}
        }
    }
}

#[zyn::element("setter")]
fn emit_setter_method(name: syn::Ident, ty: syn::Type) -> zyn::TokenStream {
    zyn::zyn! {
        pub fn {{ name | ident:"set_{}" }}(&mut self, value: {{ ty }}) {
            self.{{ name }} = value;
        }
    }
}

Namespaced Elements

Elements defined in submodules are referenced with :: path syntax:

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

zyn! {
    @components::field_decl(
        name = field.ident.clone().unwrap(),
        ty = field.ty.clone(),
    )
}

Only the last path segment is PascalCased — components::field_decl resolves to components::FieldDecl.

Organizing a Component Library

mod impls {
    #[zyn::element]
    pub fn display(name: syn::Ident) -> zyn::TokenStream {
        zyn::zyn! {
            impl ::std::fmt::Display for {{ name }} {
                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
                    write!(f, "{}", self.name)
                }
            }
        }
    }
}

mod fields {
    #[zyn::element]
    pub fn required(name: syn::Ident, ty: syn::Type) -> zyn::TokenStream {
        zyn::zyn!(pub {{ name }}: {{ ty }},)
    }

    #[zyn::element]
    pub fn optional(name: syn::Ident, ty: syn::Type) -> zyn::TokenStream {
        zyn::zyn!(pub {{ name }}: Option<{{ ty }}>,)
    }
}
zyn! {
    pub struct {{ name }} {
        @for (field in fields.iter()) {
            @if (field.is_optional) {
                @fields::optional(name = field.ident.clone().unwrap(), ty = field.ty.clone())
            } @else {
                @fields::required(name = field.ident.clone().unwrap(), ty = field.ty.clone())
            }
        }
    }

    @impls::display(name = name.clone())
}

Paths can be as deep as needed — @a::b::c::my_element(...) resolves to a::b::c::MyElement.

Elements in Loops

Elements compose naturally with @for:

zyn! {
    impl {{ struct_name }} {
        @for (field in fields.iter()) {
            @getter(
                name = field.ident.clone().unwrap(),
                ty = field.ty.clone(),
            )
            @setter(
                name = field.ident.clone().unwrap(),
                ty = field.ty.clone(),
            )
        }
    }
}

Combining with @if

zyn! {
    @for (field in fields.iter()) {
        @if (field.attrs.has_attr("skip")) {}
        @else {
            @field_decl(
                vis = field.vis.clone(),
                name = field.ident.clone().unwrap(),
                ty = field.ty.clone(),
            )
        }
    }
}

Children Blocks in Loops

zyn! {
    @for (variant in variants.iter()) {
        @arm(pattern = variant.pat.clone()) {
            Self::{{ variant.name }} => {{ variant.index }},
        }
    }
}