Getting Started
Installation
Add zyn to your proc-macro crate:
[dependencies]
zyn = "0.3"
Zyn re-exports syn, quote, and proc-macro2 — you don’t need to add them separately. Access them as zyn::syn, zyn::quote, and zyn::proc_macro2.
Feature flags
| Feature | Default | Description |
|---|---|---|
derive | yes | Proc macros (zyn!, debug!, #[zyn::element], #[zyn::pipe], #[zyn::derive], #[zyn::attribute]), extractors (Extract<T>, Attr<T>, Fields, Variants, Data<T>), diagnostics (error!, warn!, note!, help!, bail!), and #[derive(Attribute)] |
ext | no | AttrExt and AttrsExt traits for parsing syn::Attribute values |
To enable ext:
[dependencies]
zyn = { version = "0.3", features = ["ext"] }
Your first template
The zyn! macro is a template engine that returns a zyn::TokenStream. Everything outside {{ }} and @ directives passes through as literal tokens, just like quote!:
use zyn::prelude::*;
let name = &input.ident;
let tokens: zyn::TokenStream = zyn! {
pub struct {{ name }}Builder {
ready: bool,
}
};
{{ expr }} interpolates any value that implements ToTokens — identifiers, types, expressions, even other token streams.
Pipes
Pipes transform interpolated values inline with |:
zyn! {
pub fn {{ name | snake }}(&self) -> &Self {
&self
}
}
Chain multiple pipes and format identifiers:
{{ name | snake | ident:"get_{}" }}
Built-in pipes include snake, camel, pascal, screaming, kebab, upper, lower, plural, singular, trim, str, ident, and fmt. See Pipes for the full list.
Control flow
Templates support @if, @for, and @match directives:
zyn! {
impl {{ ident }} {
@for (field in fields.iter()) {
pub fn {{ field.ident | snake }}(&self) -> &{{ field.ty }} {
&self.{{ field.ident }}
}
}
}
}
Conditionals:
zyn! {
@if (is_pub) { pub } struct {{ name }} {
@for (field in fields.iter()) {
@if (field.vis == syn::Visibility::Public(Default::default())) {
{{ field.ident }}: {{ field.ty }},
}
}
}
}
See Control Flow for @match and nested directives.
Elements
When a template pattern repeats, extract it into a reusable element with #[zyn::element]:
#[zyn::element]
fn getter(name: zyn::syn::Ident, ty: zyn::syn::Type) -> zyn::TokenStream {
zyn::zyn! {
pub fn {{ name | snake | ident:"get_{}" }}(&self) -> &{{ ty }} {
&self.{{ name }}
}
}
}
Invoke elements inside templates with @:
zyn! {
impl {{ ident }} {
@for (field in fields.iter()) {
@getter(
name = field.ident.clone().unwrap(),
ty = field.ty.clone(),
)
}
}
}
Elements are compiled like function calls — they accept typed parameters and return a TokenStream. See Elements for children, extractors, and diagnostics.
Wiring it up
Use #[zyn::derive] to turn your templates into a real proc macro. Parameters marked #[zyn(input)] are automatically extracted from the derive input:
#[zyn::element]
fn getter(name: zyn::syn::Ident, ty: zyn::syn::Type) -> zyn::TokenStream {
zyn::zyn! {
pub fn {{ name | snake | ident:"get_{}" }}(&self) -> &{{ ty }} {
&self.{{ name }}
}
}
}
#[zyn::derive]
fn my_getters(
#[zyn(input)] ident: zyn::Extract<zyn::syn::Ident>,
#[zyn(input)] fields: zyn::Fields,
) -> zyn::TokenStream {
zyn::zyn! {
impl {{ ident }} {
@for (field in fields.iter()) {
@getter(
name = field.ident.clone().unwrap(),
ty = field.ty.clone(),
)
}
}
}
}
Users apply it like any derive macro — the function name my_getters becomes MyGetters:
#[derive(MyGetters)]
struct User {
name: String,
age: u32,
}
// Generated:
// impl User {
// pub fn get_name(&self) -> &String { &self.name }
// pub fn get_age(&self) -> &u32 { &self.age }
// }
See Proc Macro Entry Points for #[zyn::attribute], custom names, helper attributes, and more.
Next steps
- Templates — interpolation, control flow, pipes, case conversion
- Elements — children, extractors, input context, advanced patterns
- Diagnostics —
error!,warn!,note!,help!,bail! - derive(Attribute) — typed attribute parsing with
#[derive(Attribute)] - Proc Macro Entry Points —
#[zyn::derive]and#[zyn::attribute] - Debugging —
zyn::debug!withpretty,raw, andastmodes