Expand description
A proc macro framework with templates, composable elements, and built-in diagnostics.
cargo add zyn§Table of Contents
§Templates
The zyn! macro is the core of zyn. Write token output as if it were source code,
with {{ }} interpolation and @ control flow directives.
Interpolation — any quote::ToTokens value:
let name = zyn::format_ident!("hello_world");
zyn::zyn!(fn {{ name }}() {})
// → fn hello_world() {}Pipes — transform values inline:
zyn::zyn!(fn {{ name | pascal }}() {})
// name = "hello_world" → fn HelloWorld() {}Control flow:
zyn::zyn!(
@if (is_pub) { pub }
@for (field in fields.named.iter()) {
fn {{ field.ident }}(&self) -> &{{ field.ty }} {
&self.{{ field.ident }}
}
}
)Full template syntax:
| Syntax | Purpose |
|---|---|
{{ expr }} | Interpolate any quote::ToTokens value |
{{ expr | pipe }} | Transform value through a pipe before inserting |
@if (cond) { ... } | Conditional token emission |
@else { ... } | Else branch |
@else if (cond) { ... } | Else-if branch |
@for (x in iter) { ... } | Loop over an iterator |
@for (N) { ... } | Repeat N times |
@match (expr) { pat => { ... } } | Pattern-based emission |
@element_name(prop = val) | Invoke a #[element] component |
See zyn! for the full syntax reference.
Templates are fully type-checked at compile time — errors appear inline, just like regular Rust code.

§Elements
Elements are reusable template components defined with #[zyn::element].
They encapsulate a fragment of token output and accept typed props.
Define an 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 it inside any template with @:
zyn::zyn! {
impl {{ ident }} {
@for (field in fields.named.iter()) {
@getter(name = field.ident.clone().unwrap(), ty = field.ty.clone())
}
}
}Elements can also receive extractors — values resolved automatically from proc macro
input — by marking a param with #[zyn(input)]:
#[zyn::derive]
fn my_getters(
#[zyn(input)] ident: zyn::Extract<zyn::syn::Ident>,
#[zyn(input)] fields: zyn::Fields<zyn::syn::FieldsNamed>,
) -> zyn::TokenStream {
zyn::zyn! {
impl {{ ident }} {
@for (field in fields.named.iter()) {
pub fn {{ field.ident | snake | ident:"get_{}" }}(&self) -> &{{ field.ty }} {
&self.{{ field.ident }}
}
}
}
}
}
// Applied to: struct User { first_name: String, age: u32 }
// Generates:
// impl User {
// pub fn get_first_name(&self) -> &String { &self.first_name }
// pub fn get_age(&self) -> &u32 { &self.age }
// }See element and derive for the full reference.
§Pipes
Pipes transform interpolated values: {{ expr | pipe }}. They chain left to right:
zyn::zyn!(fn {{ name | snake | ident:"get_{}" }}() {})
// name = "HelloWorld" → fn get_hello_world() {}Built-in pipes:
| Pipe | Input example | Output |
|---|---|---|
snake | HelloWorld | hello_world |
pascal | hello_world | HelloWorld |
camel | hello_world | helloWorld |
screaming | HelloWorld | HELLO_WORLD |
kebab | HelloWorld | "hello-world" |
upper | hello | HELLO |
lower | HELLO | hello |
str | hello | "hello" |
trim | __foo__ | foo |
plural | user | users |
singular | users | user |
ident:"pattern_{}" | hello | pattern_hello (ident) |
fmt:"pattern_{}" | hello | "pattern_hello" (string) |
All built-in pipes are in the pipes module and re-exported by prelude.
Custom pipes via #[zyn::pipe]:
#[zyn::pipe]
fn shout(input: String) -> zyn::syn::Ident {
zyn::syn::Ident::new(&format!("{}_BANG", input.to_uppercase()), zyn::Span::call_site())
}
zyn::zyn!(fn {{ name | shout }}() {})
// name = "hello" → fn HELLO_BANG() {}See pipe and the Pipe trait for the full reference.
§Attributes
zyn provides two tools for attribute handling: a derive macro for typed parsing and a proc macro attribute for writing attribute macros.
Typed attribute structs via #[derive(Attribute)]:
#[derive(zyn::Attribute)]
#[zyn("builder")]
struct BuilderConfig {
#[zyn(default)]
skip: bool,
#[zyn(default = "build".to_string())]
method: String,
}
// users write: #[builder(skip)] or #[builder(method = "create")]The derive generates from_args, FromArg, and FromInput implementations, as well as
a human-readable about() string for error messages.
Auto-suggest — when a user misspells an argument name, zyn automatically suggests the closest known field. No extra setup required:
error: unknown argument `skiip`
--> src/main.rs:5:12
|
5 | #[builder(skiip)]
| ^^^^^
|
= help: did you mean `skip`?Suggestions are offered when the edit distance is ≤ 3 characters. Distant or completely unknown keys produce only the “unknown argument” error without a suggestion.
Attribute proc macros via #[zyn::attribute]:
#[zyn::attribute]
fn my_attr(#[zyn(input)] item: zyn::syn::ItemFn, args: zyn::Args) -> zyn::TokenStream {
// args: parsed key=value arguments from the attribute invocation
zyn::zyn!({ { item } })
}See Attribute and attribute for the full reference.
§Testing
§Assertions
zyn! returns Output — test both tokens and diagnostics directly:
#[test]
fn generates_function() {
let input: zyn::Input = zyn::parse!("struct Foo;" => zyn::syn::DeriveInput).unwrap().into();
let output = zyn::zyn!(fn hello() {});
let expected = zyn::quote::quote!(fn hello() {});
zyn::assert_tokens!(output, expected);
}Diagnostic assertions check error messages from error!, warn!, bail!:
#[zyn::element]
fn validated(name: zyn::syn::Ident) -> zyn::TokenStream {
if name == "forbidden" {
bail!("reserved identifier `{}`", name);
}
zyn::zyn!(fn {{ name }}() {})
}
#[test]
fn rejects_forbidden_name() {
let input: zyn::Input = zyn::parse!("struct Foo;" => zyn::syn::DeriveInput).unwrap().into();
let output = zyn::zyn!(@validated(name = zyn::format_ident!("forbidden")));
zyn::assert_diagnostic_error!(output, "reserved identifier");
zyn::assert_tokens_empty!(output);
}
#[test]
fn accepts_valid_name() {
let input: zyn::Input = zyn::parse!("struct Foo;" => zyn::syn::DeriveInput).unwrap().into();
let output = zyn::zyn!(@validated(name = zyn::format_ident!("hello")));
zyn::assert_tokens_contain!(output, "fn hello");
}| Macro | Purpose |
|---|---|
assert_tokens! | Compare two token streams |
assert_tokens_empty! | Assert no tokens produced |
assert_tokens_contain! | Check for substring in output |
assert_diagnostic_error! | Assert error diagnostic with message |
assert_diagnostic_warning! | Assert warning diagnostic |
assert_diagnostic_note! | Assert note diagnostic |
assert_diagnostic_help! | Assert help diagnostic |
assert_compile_error! | Alias for assert_diagnostic_error! |
With the pretty feature:
| Macro | Purpose |
|---|---|
assert_tokens_pretty! | Compare using prettyplease-formatted output |
assert_tokens_contain_pretty! | Substring check on pretty-printed output |
§Debugging
Add debug (or debug = "pretty") to any zyn attribute macro and set ZYN_DEBUG to
inspect generated code at compile time:
#[zyn::element(debug)]
fn greeting(name: zyn::syn::Ident) -> zyn::TokenStream {
zyn::zyn!(fn {{ name }}() {})
}ZYN_DEBUG="*" cargo buildnote: zyn::element ─── Greeting
struct Greeting { pub name : zyn :: syn :: Ident , } impl :: zyn :: Render
for Greeting { fn render (& self , input : & :: zyn :: Input) -> :: zyn ::
proc_macro2 :: TokenStream { ... } }With the pretty feature, use debug = "pretty" for formatted output:
#[zyn::element(debug = "pretty")]
fn greeting(name: zyn::syn::Ident) -> zyn::TokenStream {
zyn::zyn!(fn {{ name }}() {})
}note: zyn::element ─── Greeting
struct Greeting {
pub name: zyn::syn::Ident,
}
impl ::zyn::Render for Greeting {
fn render(&self, input: &::zyn::Input) -> ::zyn::Output { ... }
}All macros support debug: #[zyn::element], #[zyn::pipe], #[zyn::derive],
#[zyn::attribute].
ZYN_DEBUG accepts comma-separated *-wildcard patterns matched against the generated
PascalCase type name:
ZYN_DEBUG="Greeting" cargo build # exact match
ZYN_DEBUG="Greet*" cargo build # prefix wildcard
ZYN_DEBUG="*Element" cargo build # suffix wildcard
ZYN_DEBUG="Greeting,Shout" cargo build # multiple patternsSee the [test] module for the full assertion macro reference and the debug module
for programmatic access via DebugExt.
§Features
| Feature | Default | Description |
|---|---|---|
derive | ✓ | All proc macro attributes: element, pipe, derive, attribute, and Attribute |
ext | Extension traits for common syn AST types (AttrExt, FieldExt, TypeExt, etc.) — see ext | |
pretty | Pretty-printed token output in debug mode — see debug | |
diagnostics | Error accumulation — collect multiple errors before aborting — see mark |
§ext
The ext module adds ergonomic extension traits for navigating syn AST types.
zyn = { features = ["ext"] }use zyn::ext::{AttrExt, TypeExt};
// check and read attribute arguments
if attr.is("serde") {
let rename: Option<_> = attr.get("rename"); // → Some(Meta::NameValue)
let skip: bool = attr.exists("skip");
}
// inspect field types
if field.is_option() {
let inner = field.inner_type().unwrap();
}See the ext module for all available traits.
§pretty
The pretty feature enables pretty-printed output in debug mode, formatting
generated token streams as readable Rust source code via prettyplease.
zyn = { features = ["pretty"] }Enable debug output per-element with the debug or debug = "pretty" argument,
then set ZYN_DEBUG="*" at build time:
#[zyn::element(debug = "pretty")]
fn my_element(name: zyn::syn::Ident) -> zyn::TokenStream {
zyn::zyn!(struct {{ name }} {})
}ZYN_DEBUG="*" cargo buildnote: zyn::element ─── my_element
struct MyElement {
pub name: zyn::syn::Ident,
}
impl ::zyn::Render for MyElement {
fn render(&self, input: &::zyn::Input) -> ::zyn::Output {
...
}
}An element annotated with
debug— the argument is inert untilZYN_DEBUGis set.

Raw debug output shown as an inline compiler diagnostic in the editor.

The same raw output surfaced in the Problems panel for easy navigation.

Pretty-printed debug output — formatted with
prettypleasefor readable, indented Rust code.

See the debug module for programmatic access via DebugExt.
§diagnostics
The diagnostics feature enables error accumulation — collecting multiple compiler
errors before aborting, so users see all problems at once instead of one at a time.
zyn = { features = ["diagnostics"] }Inside any #[zyn::element], #[zyn::derive], or #[zyn::attribute] body, use the
built-in diagnostic macros directly — no setup required:
#[zyn::element]
fn my_element(name: zyn::syn::Ident) -> zyn::TokenStream {
if name == "forbidden" {
bail!("reserved identifier `{}`", name);
}
if name.to_string().starts_with('_') {
warn!("identifiers starting with `_` are conventionally unused");
}
zyn::zyn!(fn {{ name }}() {})
}| Macro | Level | Behaviour |
|---|---|---|
error!(msg) | error | accumulates, does not stop execution |
warn!(msg) | warning | accumulates, does not stop execution |
note!(msg) | note | accumulates, does not stop execution |
help!(msg) | help | accumulates, does not stop execution |
bail!(msg) | error | accumulates and immediately returns |
All accumulated diagnostics are emitted together at the end of the element or macro body, so users see every error at once instead of fixing them one by one.
error: reserved identifier `forbidden`
--> src/main.rs:3:1
error: reserved identifier `forbidden`
--> src/main.rs:7:1See the mark module for the lower-level diagnostic builder API.
Modules§
- ast
- Template AST node types. Template AST node types.
- case
- Case conversion utilities. Case conversion functions and macros.
- debug
- Debug formatting and printing for template expansions. Debug formatting utilities for macro expansions.
- ext
- Extension traits for common
synAST types. Extension traits for commonsynAST types. - extract
- Extractors for resolving values from proc macro input. Extractors for resolving typed values from proc macro input.
- ident
- Internal identifier generation for template expansion. Internal identifier generation for template expansion.
- mark
- Types and implementations for marking spans. Diagnostic construction utilities.
- meta
- Attribute argument parsing types. Attribute argument parsing types.
- path
- Dot-separated path type for navigating nested
synmetadata. Dot-separated path type for navigating nestedsynmetadata. - pipes
- Built-in pipe types for template value transforms. Built-in pipe transforms for template value interpolation.
- prelude
- The zyn prelude. Re-exports all built-in pipes, core traits, and proc macros.
- proc_
macro2 - github crates-io docs-rs
- quote
- github crates-io docs-rs
- syn
- github crates-io docs-rs
- test
- Test utilities for the zyn proc macro framework.
Macros§
- assert_
compile_ error - Asserts that an
Outputhas an error-level diagnostic containing the given message. - assert_
diagnostic - Asserts that an
Outputcontains a diagnostic at the given level whoseDisplayoutput contains the expected substring. - assert_
diagnostic_ error - Asserts that an
Outputcontains an error diagnostic with the given message. - assert_
diagnostic_ help - Asserts that an
Outputcontains a help diagnostic with the given message. - assert_
diagnostic_ note - Asserts that an
Outputcontains a note diagnostic with the given message. - assert_
diagnostic_ warning - Asserts that an
Outputcontains a warning diagnostic with the given message. - assert_
tokens - Asserts that two token streams produce identical output.
- assert_
tokens_ contain - Asserts that the token output contains the given substring.
- assert_
tokens_ contain_ pretty - Asserts that the pretty-printed token output contains the given substring.
- assert_
tokens_ empty - Asserts that a token stream or
Outputproduces no tokens. - assert_
tokens_ pretty - Asserts that two token streams produce identical output when pretty-printed.
- camel
- Converts a string or ident to camelCase.
- format_
ident - Formatting macro for constructing
Idents. - kebab
- Converts a string or ident to kebab-case.
- parse
- Parses tokens or string literals into a type. Wraps
syn::parse_strandsyn::parse2. - parse_
input - Parses a
proc_macro::TokenStreamin a proc macro entry point. Wrapssyn::parse_macro_input!. - pascal
- Converts a string or ident to PascalCase.
- screaming
- Converts a string or ident to SCREAMING_SNAKE_CASE.
- snake
- Converts a string or ident to snake_case.
- zyn
- Expands a zyn template into a [
proc_macro2::TokenStream].
Structs§
- Args
- A parsed list of attribute arguments.
- Attr
- Element extractor that resolves a
#[derive(Attribute)]struct from the input’s attributes. - Data
- Element extractor that pulls the
syn::Datafrom a derive input. - Diagnostic
- A compiler diagnostic (error, warning, note, or help message).
- Extract
- Generic extractor wrapper — delegates to
T::from_input. - Fields
- Element extractor that pulls struct fields from the input.
- Output
- The result of rendering an element or expanding a template.
- Output
Builder - Builder for constructing
Outputinstances. - Span
- A region of source code, along with macro expansion information.
- Template
- A parsed template AST. Created by parsing template syntax via
syn::parse2::<Template>(tokens). - Token
Stream - An abstract stream of tokens, or more concretely a sequence of token trees.
- Variants
- Element extractor that pulls enum variants from the input.
Enums§
- Arg
- A single parsed attribute argument.
- Input
- The proc macro input context. Wraps either a
DeriveInputor anItem.
Traits§
- Expand
- Internal trait for AST node expansion. Not part of the public API.
- FromArg
- Converts a single
Arginto a typed value. - From
Data - Converts
syn::Datainto a specific data representation. - From
Fields - Converts
syn::Fieldsinto a specific fields representation. - From
Input - Extracts a value from the macro input context.
- Pipe
- Implemented by
#[zyn::pipe]types. Transforms a value in a pipe chain. - Render
- Implemented by
#[zyn::element]types. Renders the element with the givenInputcontext. - ToTokens
- Types that can be interpolated inside a
quote!invocation.
Functions§
- closest_
match - Returns the closest matching string from
haystackif the edit distance is at most 3. Used for “did you mean?” suggestions in error messages. - levenshtein
- Computes the Levenshtein edit distance between two strings.
Type Aliases§
Attribute Macros§
- attribute
- Defines an attribute macro entry point that auto-parses the annotated item into typed inputs.
- derive
- Defines a derive macro entry point that auto-parses
DeriveInputinto typed inputs. - element
- Defines a reusable template component generating a struct that implements
Render. - pipe
- Defines a custom pipe transform used inside
{{ expr | pipe }}interpolations.
Derive Macros§
- Attribute
- Derives typed attribute parsing from
#[attr(...)]key-value syntax.