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

These Macros Should Be Illegal

The API reference with tested examples is on docs.rs.

This crate is a small collection of experimental Rust macros for deleting boilerplate and trying syntax that ordinary Rust—quite reasonably—does not accept. The useful bits come first; the cursed implementation details are here when you actually need them.

cargo add these-macros-should-be-illegal

Import only the macros used at each call site. For example:

use these_macros_should_be_illegal::{
    callable, enum_fn, make_fn, perm, qf, reflect, stringify_as, strutuct,
};

Pick your crime

  • discriminated_str assigns each variant a unique string and generates a literal-selected delegating constructor.
  • enum_fn generates a method from inline per-variant match-arm expressions.
  • callable and make_fn! give a local object function-like syntax without erasing its type or its ordinary methods.
  • strutuct!, with emmun! as an exact alias, declares a small family of nested algebraic types in one place and generates constructor macros for them.
  • The stringify_* macros turn identifiers, string literals, and Rust types into compile-time string literals.
  • forward_attributes makes outer attributes usable as function-like macro configuration.
  • qf! gives common standard-library types their fully qualified paths inside generated code.
  • shared_match_arms! shares an RHS between patterns whose bindings have different concrete types.
  • literally_literal_string! and expand! let token rewriting handle syntax that Rust cannot parse as an ordinary source file.
  • reflect! and perm! transform macro invocation and token-tree structure without assigning a Rust grammar to the opaque payload.

The macro map groups every public entry point by what it consumes and produces. The individual pages then give the complete accepted syntax, generated shape, and current boundaries.

Stability

This is a personal stable-Rust macro laboratory. The APIs are intentionally small and experimental, and may evolve whenever a better crime presents itself. Releases are available from crates.io, and the ordinary API reference lives on docs.rs.

Macros

The public macros fall into five families. The distinction is about what each macro is allowed to assume about its input, not about which procedural-macro kind happens to implement it.

Structured local syntax

These macros parse a known local shape and generate ordinary Rust:

  • callable and make_fn! give objects function-like macro syntax while retaining their original types and methods;
  • discriminated_str creates a unique string mapping and a literal-selected constructor macro;
  • enum_fn turns per-variant expressions into a method;
  • strutuct!, also exported as emmun!, generates related structs, enums, and constructor macros;
  • overload_op repeats one operator written for references across its owned operand forms and its augmented assignment;
  • complete_ops adds the operators that follow from the ones a type already has, such as subtraction from addition and negation.

Ordinary Rust fragments and names

These conveniences accept one ordinary Rust fragment or name:

Recursive whole-stream extensions

These macros rewrite arbitrary token trees recursively without assuming that the complete input is an item or expression:

Read How token rewriting behaves before combining the whole-stream extensions with attributes or macros that consume private syntax.

Composition helpers

These helpers transport or protect token streams for another macro:

  • forward_attributes moves an item-position macro invocation’s outer attributes behind a ; boundary in its opaque input;
  • excluded_macros marks macro invocations whose contents a recursive transformation must leave opaque;
  • expand! loads one out-of-line module and wraps its body in one or more whole-stream transformations before rustc parses it.

Meta-transformers

These macros transform macro and token-stream structure itself:

  • reflect! exchanges two nested invocation nodes around an opaque body;
  • perm! applies a finite permutation to comma-separated token trees while fixing the remaining positions.

Function-like syntax for callable objects

API reference on docs.rs: callable, make_fn!.

callable gives one method of a user-owned trait the structural alias __priv_tmsbi_call. make_fn! can then bind any implementation and generate a same-name macro which forwards its arguments to that alias. No shared callable trait is imposed by this crate.

#[callable(apply)]
trait Action {
    fn apply(&self, point: usize) -> usize;
}

#[derive(Clone)]
struct Shift(usize);

impl Action for Shift {
    fn apply(&self, point: usize) -> usize {
        point + self.0
    }
}

impl Shift {
    fn inverse(&self) -> Shift {
        Shift(usize::MAX - self.0 + 1)
    }
}

make_fn!(sigma = Shift(3));

assert_eq!(sigma!(2), 5);
let _inverse = sigma.inverse();

The macro and value occupy different namespaces. Consequently sigma!(2) can expand to sigma.__priv_tmsbi_call(2) while ordinary methods remain available on sigma.

The generated alias is a default trait method. Existing implementations only implement the method selected by the attribute. Its receiver, arguments, generics, return type, bounds, and async or unsafe behavior are retained.

Mutable callable methods require a mutable binding:

#[callable(advance)]
trait Advance {
    fn advance(&mut self, amount: usize) -> usize;
}

struct Counter(usize);

impl Advance for Counter {
    fn advance(&mut self, amount: usize) -> usize {
        self.0 += amount;
        self.0
    }
}

make_fn!(mut counter = Counter(0));
assert_eq!(counter!(2), 2);
assert_eq!(counter!(3), 5);

make_fn! creates a let binding and is therefore intended for statement position inside a function or block. Method resolution must find exactly one accessible __priv_tmsbi_call; implementing multiple registered callable traits for one type deliberately makes the sugar ambiguous.

discriminated_str

API reference on docs.rs: discriminated_str.

Assigns every enum variant one unique string literal. The generated method maps values to strings; a same-name macro maps literals and payloads back to variant constructors.

You write Roughly expands to
#[discriminated_str(name)]
enum Token {
    Ident(String) = "ident",
    End = "end",
}

fn main() {
    let token = Token!("ident", String::from("value"));
    assert_eq!(token.name(), "ident");
    assert!(matches!(Token!("end"), Token::End));
}
enum Token {
    Ident(String),
    End,
}

impl Token {
    const fn name(&self) -> &'static str {
        match self {
            Self::Ident(..) => "ident",
            Self::End => "end",
        }
    }
}

// Generated in the macro namespace:
macro_rules! Token {
    ("ident", $value:expr) => { Token::Ident($value) };
    ("end") => { Token::End };
}

The constructor macro follows the original variant shape:

You write Roughly expands to
#[discriminated_str(code)]
enum Error {
    Io(std::io::Error) = "io",
    Parse { offset: usize } = "parse",
    Unknown = "unknown",
}

fn main() {
    let error = Error!("parse", offset: 17);
    assert_eq!(error.code(), "parse");
}
Error!("io", source)
// Error::Io(source)

Error!("parse", offset: 17)
// Error::Parse { offset: 17 }

Error!("unknown")
// Error::Unknown

Every variant must have a string literal, and literals must be unique. The constructor direction deliberately accepts literals rather than runtime &str values: it selects a constructor during macro expansion and delegates the remaining expressions directly to it. No generated runtime tag type leaks into the user’s namespace.

Generics, lifetimes, visibility, and where clauses remain on the original enum. cfg and cfg_attr are retained on the generated forward match arms.

enum_fn

API reference on docs.rs: enum_fn.

Generates an enum method as an inline match expression.

The attribute argument names the generated method and gives its return type. An expression after a variant becomes that match arm’s value:

You write Roughly expands to
#[enum_fn(description: &'static str)]
enum Action {
    Quit = "quit the application",
    Submit = "evaluate the input",
}

const QUIT: &str = Action::Quit.description();
assert_eq!(QUIT, "quit the application");
enum Action {
    Quit,
    Submit,
}

impl Action {
    const fn description(&self) -> &'static str {
        match self {
            Self::Quit => "quit the application",
            Self::Submit => "evaluate the input",
        }
    }
}

const QUIT: &str = Action::Quit.description();
assert_eq!(QUIT, "quit the application");

Field projections

An integer RHS selects a zero-based tuple field and an identifier matching a named field selects that field. The generated method borrows self, so the selected expression is a shared field binding:

You write Roughly expands to
#[enum_fn(description: &str)]
enum Message<'a> {
    Fixed = "fixed text",
    Owned(String) = 0,
    Pair(u8, &'a str) = 1,
    Named { code: u8, text: String } = text,
}

assert_eq!(Message::Owned("owned".into()).description(), "owned");
assert_eq!(Message::Pair(7, "second").description(), "second");
assert_eq!(
    Message::Named { code: 9, text: "named".into() }.description(),
    "named",
);
enum Message<'a> {
    Fixed,
    Owned(String),
    Pair(u8, &'a str),
    Named { code: u8, text: String },
}

impl Message<'_> {
    fn description(&self) -> &str {
        match self {
            Self::Fixed => "fixed text",
            Self::Owned(value) => value,
            Self::Pair(_, value) => value,
            Self::Named { text, .. } => text,
        }
    }
}

assert_eq!(Message::Owned("owned".into()).description(), "owned");
assert_eq!(Message::Pair(7, "second").description(), "second");

The field may have any type; Rust checks it against the declared return type. Borrowed results are tied to the borrow of self.

A closure RHS instead receives every field in declaration order and computes the result from the complete product. Any number of arguments, including zero, is supported. Rust checks its arity, body, and return type:

You write Roughly expands to
#[enum_fn(description: String)]
enum Message {
    Pair(String, String) = |left, right| format!("{left}: {right}"),
    Unit = || String::from("unit"),
}

assert_eq!(
    Message::Pair("hello".into(), "world".into()).description(),
    "hello: world",
);
assert_eq!(Message::Unit.description(), "unit");
enum Message {
    Pair(String, String),
    Unit,
}

impl Message {
    fn description(&self) -> String {
        match self {
            Self::Pair(left, right) => format!("{left}: {right}"),
            Self::Unit => String::from("unit"),
        }
    }
}

assert_eq!(
    Message::Pair("hello".into(), "world".into()).description(),
    "hello: world",
);

Because the generated accessor matches on &self, closure arguments receive the corresponding shared field bindings.

Missing arms

If any variant has no RHS, the declared return type is lifted into Option. When every present expression is const-compatible, the optional method remains a const fn:

You write Roughly expands to
#[enum_fn(description: &'static str)]
enum Status {
    Ready = "ready",
    Unknown,
}

const READY: Option<&str> = Status::Ready.description();
const UNKNOWN: Option<&str> = Status::Unknown.description();

assert_eq!(READY, Some("ready"));
assert_eq!(UNKNOWN, None);
enum Status {
    Ready,
    Unknown,
}

impl Status {
    const fn description(&self) -> Option<&'static str> {
        match self {
            Self::Ready => Some("ready"),
            Self::Unknown => None,
        }
    }
}

const READY: Option<&str> = Status::Ready.description();
const UNKNOWN: Option<&str> = Status::Unknown.description();

Use = stringify on the attribute to return a missing variant’s Rust name instead:

You write Roughly expands to
#[enum_fn(description: &'static str = stringify)]
enum Status {
    Ready = "ready",
    Unknown,
}

assert_eq!(Status::Unknown.description(), "Unknown");
enum Status {
    Ready,
    Unknown,
}

impl Status {
    const fn description(&self) -> &'static str {
        match self {
            Self::Ready => "ready",
            Self::Unknown => "Unknown",
        }
    }
}

assert_eq!(Status::Unknown.description(), "Unknown");

Use = panic when reaching an undescribed variant is a bug. The method keeps the declared return type, and the missing arms panic with the variant’s name:

You write Roughly expands to
#[enum_fn(description: &'static str = panic)]
enum Status {
    Ready = "ready",
    Unknown,
}

assert_eq!(Status::Ready.description(), "ready");
Status::Unknown.description();
#![allow(unused)]
fn main() {
enum Status {
    Ready,
    Unknown,
}

impl Status {
    const fn description(&self) -> &'static str {
        match self {
            Self::Ready => "ready",
            Self::Unknown => panic!("variant `Unknown` has no generated value"),
        }
    }
}

assert_eq!(Status::Ready.description(), "ready");
Status::Unknown.description();
}

Generic enums and ordinary Rust structure

The original enum declaration remains an ordinary syn::ItemEnum. Attributes, visibility, generics, bounds, lifetimes, where clauses, and field types are retained. Conditional attributes are copied to the corresponding generated match arms so the enum and method stay exhaustive under the same configuration.

#[enum_fn(size: usize)]
pub(crate) enum Value<'a, T: AsRef<str>, const N: usize>
where
    T: core::fmt::Debug,
{
    Text(&'a T) = |text| (*text).as_ref().len(),
    Bytes([u8; N]) = |bytes| bytes.len(),
    Function(fn(u8) -> u8) = |function| usize::from(function(2)),
    #[cfg(any())]
    Disabled = 0,
}

fn increment(value: u8) -> u8 {
    value + 1
}

let text = String::from("hello");
assert_eq!(Value::<String, 3>::Text(&text).size(), 5);
assert_eq!(Value::<String, 3>::Bytes([1, 2, 3]).size(), 3);
assert_eq!(Value::<String, 3>::Function(increment).size(), 3);

Tuple fields, arrays, raw pointers, function pointers, associated-type projections, and blocks are ordinary Rust in this input. An integer selector is validated against the selected tuple product before code generation.

Arbitrary output types

The declared type is arbitrary. Expressions and closures must produce it themselves; enum_fn performs no string conversion or cloning. Optionality is inferred independently from the declared return type:

You write Roughly expands to
#[enum_fn(description: String)]
enum Error {
    Message(String) = |message| (*message).clone(),
    Missing,
}

assert_eq!(
    Error::Message("details".into()).description(),
    Some(String::from("details")),
);
assert_eq!(Error::Missing.description(), None);
enum Error {
    Message(String),
    Missing,
}

impl Error {
    fn description(&self) -> Option<String> {
        match self {
            Self::Message(value) => Some(value.clone()),
            Self::Missing => None,
        }
    }
}

assert_eq!(Error::Missing.description(), None);

An explicitly const closure uses const { |arguments| expression }. Its body is inlined into the match arm and therefore does not downgrade the generated method from const fn; an ordinary closure does.

strutuct!

API reference on docs.rs: strutuct!, emmun!.

strutuct! keeps small related types where they are used, then hoists them into ordinary Rust declarations in dependency order. Generated declarations and fields are public by default. emmun! is an exact alias with the same syntax.

You write Roughly expands to
strutuct! {
    Request
    method: Method { Get, Post },
    body: String?,
}
pub enum RequestMethod {
    Get,
    Post,
}

pub struct Request {
    pub method: RequestMethod,
    pub body: Option<String>,
}

// RequestMethod!(...) and Request!(...)
// constructor macros are generated too.

Outer attributes can configure the complete generated family through forward_attributes:

You write Roughly expands to
#[forward_attributes]
#[derive(Debug, PartialEq)]
strutuct! {
    Forwarded { First, Second }
}

fn main() {
    assert_eq!(Forwarded::First, Forwarded::First);
}
#[derive(Debug, PartialEq)]
pub enum Forwarded {
    First,
    Second,
}

fn main() {
    assert_eq!(Forwarded::First, Forwarded::First);
}
You write Roughly expands to
strutuct! {
    Request
    method: Method { Get, Post, Delete },
    body: String?,
}

let request = Request!(
    method: RequestMethod!(Post),
    body: Some("payload".to_owned()),
);

assert!(matches!(request.method, RequestMethod::Post));
pub enum RequestMethod {
    Get,
    Post,
    Delete,
}

pub struct Request {
    pub method: RequestMethod,
    pub body: Option<String>,
}

let request = Request {
    method: RequestMethod::Post,
    body: Some("payload".to_owned()),
};

assert!(matches!(request.method, RequestMethod::Post));

This generates the RequestMethod enum, the Request struct, and same-name constructor macros. Braced Method { ... } is a relative generated name; write |Method| { ... } when the generated enum must be named exactly Method.

Declaration forms

The forms below are the complete set of nominal declarations introduced by strutuct! itself. Ordinary Rust types may still be used as payloads, but they do not change how a declaration body is classified.

Root declarations

A nonempty body produces a named-field struct, a tuple struct, or an enum. The body shape selects which one:

strutuct! {
    struct Record {
        value: u8,
        flag: bool,
    }
}

strutuct! {
    struct Pair {
        (u8, u16)
    }
}

strutuct! {
    enum Choice {
        First,
        Second,
    }
}

A body beginning with name: Type is a named-field struct. A body consisting of exactly one parenthesized list of two or more types is a tuple struct. Every other nonempty body is an enum.

The struct and enum keywords are optional checks on the inferred shape. They do not force a body to have that shape.

Restrictions

A root declaration cannot be empty or unit-like:

strutuct! {
    Empty {}
}

A written keyword must agree with the inferred body:

strutuct! {
    enum NotAnEnum {
        value: u8,
    }
}
strutuct! {
    struct NotAStruct {
        First,
        Second,
    }
}

Named-field structs

Once the first member has the form name: Type, every member must be a named field. A field type may itself declare a relative type, an exactly named type, or an exactly named unit struct:

pub struct Existing;

strutuct! {
    Structs {
        plain: Existing,
        relative: Relative {
            value: Existing,
        },
        exact: |Exact| {
            value: Existing,
        },
        marker: |Marker|,
    }
}

This declares StructsRelative, Exact, and the unit struct Marker in addition to Structs.

Nested braced bodies use the same inference rules as roots. Consequently a field may declare any non-unit shape:

pub struct Existing;

strutuct! {
    Container {
        record: Record { value: Existing },
        pair: Pair { (Existing, Existing) },
        choice: Choice { First, Second },
    }
}

Restrictions

Struct fields require commas. Once a body is inferred as a struct, an enum-shaped member cannot be mixed into it:

strutuct! {
    Mixed {
        value: u8,
        Variant,
    }
}

A relative name requires a nonempty braced body. Only an exact |Name| may declare a unit struct:

strutuct! {
    Holder {
        marker: Marker {},
    }
}

Tuple structs

A tuple declaration is one complete parenthesized product containing at least two types. The rule is identical at the root and inside another declaration:

pub struct Left;
pub struct Right;

strutuct! {
    Pair {
        (Left, Right)
    }
}

strutuct! {
    RelativeHolder {
        pair: Pair { (Left, Right) },
    }
}

strutuct! {
    ExactHolder {
        pair: |ExactPair| { (Left, Right) },
    }
}

These declarations produce Pair, RelativeHolderPair, and ExactPair as tuple structs.

Restrictions

There are no generated zero-field or one-field tuple structs. A single parenthesized type is instead the implicit enum-variant form:

pub struct Existing;

strutuct! {
    struct OneField {
        (Existing)
    }
}

An empty product is not a declaration body:

strutuct! {
    ZeroFields {
        ()
    }
}

The product must be the entire body. Adding another member makes the body an enum body, where an implicit parenthesized variant may contain only one type:

strutuct! {
    PairAndMore {
        (u8, u16),
        More,
    }
}

Enums

An enum accepts unit variants, tuple-like variants, existing implicit payload types, and generated payload declarations:

pub struct Existing;

strutuct! {
    Choice {
        Unit,
        EmptyTuple(),
        ExistingPayload(Existing),
        Product(Existing, Existing),
        (Existing),

        Record { value: Existing },
        Pair { (Existing, Existing) },
        Nested { First, Second },

        Named |Payload| { First, Second },
        |Implicit| { First, Second },
        NamedMarker |Marker|,
        |ImplicitMarker|,
    }
}

The forms mean:

Unit                          unit variant
EmptyTuple()                  empty tuple-like variant
ExistingPayload(Existing)     named variant using an existing type
Product(Existing, Existing)   named multi-field variant
(Existing)                    existing payload with an inferred variant name

Record { value: Existing }    variant plus relative named-field payload
Pair { (Existing, Existing) } variant plus relative tuple payload
Nested { First, Second }      variant plus relative enum payload

Named |Payload| { ... }       named variant plus exact payload declaration
|Implicit| { ... }            exact payload and inferred variant name
NamedMarker |Marker|          named variant plus exact unit payload
|ImplicitMarker|              exact unit payload and inferred variant name

(Existing) uses an already declared type. |Implicit| { ... } declares a new type. Parentheses and vertical bars are therefore not interchangeable.

Commas between enum variants are optional when the next variant is already structurally recognizable:

pub struct Existing;

strutuct! {
    Punctuation {
        First
        Second(Existing)
        Third { A B }
    }
}

Name and Name() are both legal and distinct, just as they are in an ordinary Rust enum.

Restrictions

An implicit parenthesized variant accepts exactly one type:

strutuct! {
    BadImplicit {
        (u8, u16)
        Unit
    }
}

Its type must also have a final path segment from which a variant name can be formed. Name a non-path payload explicitly:

strutuct! {
    NoInferredName {
        ((u8, u16))
    }
}

Parentheses never declare a generated payload type. The former contextual spellings are rejected:

strutuct! {
    OldSpelling {
        Named(Payload) { First, Second }
    }
}
strutuct! {
    OldImplicit {
        (Payload) { First, Second }
    }
}

Braced payload declarations must be nonempty:

strutuct! {
    EmptyPayload {
        Payload {}
    }
}

Rust-style enum discriminants are not part of the strutuct! declaration grammar:

strutuct! {
    Discriminants {
        First = 1,
        Second = 2,
    }
}

Relative and exact names

An unbarred nested declaration name is relative to its generated parent. Nesting continues to concatenate names:

strutuct! {
    Root {
        Relative {
            Leaf { value: u8 }
        }
        Named |Exact| {
            Leaf { value: u8 }
        }
    }
}

This declares RootRelative and RootRelativeLeaf. The bars reset the name, so the other branch declares Exact and ExactLeaf.

An exact unit declaration is legal only where a nested type is expected:

strutuct! {
    Holder {
        marker: |Marker|,
    }
}

Restrictions

The root name is already exact and is written without bars:

strutuct! {
    |Root|
}

An exact name is one identifier, not a path, and generated declarations do not accept generic parameter, bound, or where clauses:

strutuct! {
    Generic<T> {
        value: T,
    }
}

Attributes, visibility, and shape keywords

Attributes and visibility may precede roots, named fields, and inline generated declarations. A declaration keyword may be added before an inline declaration to validate its inferred shape:

#[derive(Debug)]
pub struct Existing;

strutuct! {
    #[derive(Debug)]
    pub struct Root {
        #[doc = "A private generated branch."]
        pub branch: #[derive(Clone)] priv enum Branch {
            First,
            Second,
        },
        marker: struct |Marker|,
        existing: Existing,
    }
}

Here pub before branch is the visibility of the generated Rust field; priv before enum Branch is the visibility of RootBranch. Attributes before the field and attributes before its generated type likewise have different targets.

The keyword is still only a validator:

strutuct! {
    Root {
        choice: struct Choice { First, Second },
    }
}

Tuple declaration components are currently type expressions only: unlike named fields, they do not have DSL positions for individual field attributes or visibility.

Member visibility and shape keywords

A visibility before an enum member applies to the payload type that member generates. Rust variants always share their enum’s visibility, so a visibility on a member that generates nothing is rejected rather than ignored:

strutuct! {
    VisibilityOnPlainVariants {
        // error: visibility applies to a generated payload type
        pub Unit,
        priv Existing(u8),
    }
}

A shape keyword decorates any payload the member generates, relative or exact, and is checked against the shape inferred from its body:

strutuct! {
    ExactPayloadWithKeyword {
        enum |Payload| { First, Second },
        struct Named |Marker|,
    }
}

let exact = ExactPayloadWithKeyword::PayloadExactPayloadWithKeyword(Payload::First);
let named = ExactPayloadWithKeyword::Named(Marker);
assert!(matches!(exact, ExactPayloadWithKeyword::PayloadExactPayloadWithKeyword(Payload::First)));
assert!(matches!(named, ExactPayloadWithKeyword::Named(Marker)));

Disambiguation

The parser resolves every declaration body in this order:

  1. A leading name: Type makes the complete body a named-field struct.
  2. Otherwise, one complete (A, B, ...) body with at least two types makes a tuple struct.
  3. Every other nonempty body is an enum.

The important neighboring forms are therefore:

pub struct Existing;

strutuct! { Named { value: Existing } }
strutuct! { Tuple { (Existing, Existing) } }
strutuct! { Sum { First, Second } }
strutuct! { OneParenthesized { (Existing) } }
strutuct! { NamedVariant { Pair(Existing, Existing) } }
strutuct! { EmptyTupleVariant { Empty() } }

OneParenthesized is an enum, not a one-field tuple struct. NamedVariant is an enum because Pair precedes the parentheses. EmptyTupleVariant is an enum with one fieldless tuple-like variant; it is not an empty declaration.

This precedence leaves no token sequence with two possible declaration shapes. Some neighboring spellings intentionally mean different things, and invalid mixtures fail instead of falling through to another shape.

Declaration shapes

The body shape decides what gets generated:

  • leading name: Type members form a struct;
  • one parenthesized product containing at least two types forms a tuple struct;
  • anything else forms an enum.

Nested { ... } bodies use the same rules recursively.

The root body may either follow its name directly or use ordinary declaration braces. Optional struct and enum keywords make the input look more like Rust; shape inference still decides the output, and a mismatched keyword is diagnosed:

You write Roughly expands to
strutuct! {
    pub struct Request {
        method: enum Method { Get, Post, Delete },
        body: String?,
    }
}
pub enum RequestMethod {
    Get,
    Post,
    Delete,
}

pub struct Request {
    pub method: RequestMethod,
    pub body: Option<String>,
}

Standard Rust visibility forms and priv are accepted before generated declarations and named fields. priv is the explicit private counterpart to the macro’s default-public behavior.

Nested declarations can also appear inside ordinary generic types. The generated type is hoisted as usual, while the surrounding container stays untouched:

You write Roughly expands to
struct Delimited<T>(T);

strutuct! {
    Arguments
    content: Delimited<Option<|ArgumentListContent| {
        Empty,
        Values(Vec<String>),
    }>>,
}

let arguments = Arguments!(
    content: Delimited(Some(ArgumentListContent!(Empty))),
);

assert!(matches!(
    arguments.content,
    Delimited(Some(ArgumentListContent::Empty)),
));
struct Delimited<T>(T);

pub enum ArgumentListContent {
    Empty,
    Values(Vec<String>),
}

pub struct Arguments {
    pub content: Delimited<Option<ArgumentListContent>>,
}

let arguments = Arguments {
    content: Delimited(Some(ArgumentListContent::Empty)),
};

assert!(matches!(
    arguments.content,
    Delimited(Some(ArgumentListContent::Empty)),
));

This works recursively through generic arguments, tuples, arrays, references, and other grouped type syntax. A declaration inside a type macro invocation is left to that macro instead of being hoisted by strutuct!.

Keywords, visibility, attributes, and local configuration work inside generic arguments as well. For example, Vec<priv enum |Choice| { A, B }> hoists a private Choice enum and leaves the field type as Vec<Choice>.

Enum grammar

Parentheses refer to terminal payload types that already exist. Vertical bars declare the generated payload type’s name.

Name |Type| { ... }  defines Type and emits Name(Type)
Name |Type|          defines unit Type and emits Name(Type)
Name { ... }         defines ParentName and emits Name(ParentName)
|Type| { ... }       defines Type and emits TypeParent(Type)
|Type|               defines unit Type and emits TypeParent(Type)
Name(Type)           uses Type and stays Name(Type)
Name                 stays Name
(Type)               uses Type and becomes TypeParent(Type)

A bare |Type| generates the nominal unit struct pub struct Type;: this is the () case, not the uninhabited ! case. Struct fields follow the same distinction: field: Type uses an existing Rust type, field: Name { ... } generates ParentName, and field: |Type| { ... } generates the exact name Type. The bar spelling also generates an exact unit field type without a body:

You write Roughly expands to
strutuct! {
    State
    marker: |EmptyState|,
}

let state = State!(marker: EmptyState);
pub struct EmptyState;

pub struct State {
    pub marker: EmptyState,
}

let state = State {
    marker: EmptyState,
};

Products and existing Rust types

By default, a multi-field enum variant carries one tuple product:

You write Roughly expands to
strutuct! {
    Value
    Pair(String, u8)
}

let value = Value!(Pair("answer".to_owned(), 42));
assert!(matches!(value, Value::Pair((_, 42))));
pub enum Value {
    Pair((String, u8)),
}

let value = Value::Pair(("answer".to_owned(), 42));
assert!(matches!(value, Value::Pair((_, 42))));

Disable that lowering for a declaration family when ordinary Rust variants are more useful:

You write Roughly expands to
strutuct! {
    #[strutuct(product_variants = false)]
    Value
    Pair(String, String)
    Span { start: usize, end: usize }
    #[strutuct(product_variants = true)]
    StillPacked(String, String)
}
pub struct ValueSpan {
    pub start: usize,
    pub end: usize,
}

pub enum Value {
    Pair(String, String),
    Span(ValueSpan),
    StillPacked((String, String)),
}

The same configuration attribute before a variant overrides the family setting for that variant.

Configuration and visibility

Every option can be set on the root declaration or locally before one field or variant branch:

You write Roughly expands to
strutuct! {
    #[strutuct(public = false, reverse_concat = true)]
    struct Syntax {
        hidden: enum Hidden { A, B },
        #[strutuct(public = true)]
        pub visible: pub enum Visible { A, B },
    }
}
enum HiddenSyntax {
    A,
    B,
}

pub enum VisibleSyntax {
    A,
    B,
}

struct Syntax {
    hidden: HiddenSyntax,
    pub visible: VisibleSyntax,
}

The available options are:

  • inclusions = true | false generates consuming functions for enum constructor paths; it is disabled by default;
  • product_variants = true | false selects packed products versus ordinary multi-field enum variants;
  • public = true | false selects the default visibility for that declaration branch; false restores Rust’s ordinary private-by-default behavior;
  • reverse_concat = true | false reverses every automatically concatenated name in that branch. For example, ParentName becomes NameParent, while explicit names between |...| stay unchanged.

These are the complete options currently implemented. More can be added without changing the declaration grammar.

An explicit pub, restricted pub(...), or priv wins for that individual declaration or field. Local configuration is inherited by generated declarations below that object, while siblings retain their surrounding configuration.

Parsing precedence

strutuct! is a syntax extension, so its grammar wins whenever its tokens could also be interpreted as unusually shaped Rust. For example, postfix ? and * are always the macro’s Option and Box constructors in a type position, and a braced identifier is always an inline declaration. The syntax is deliberately chosen to avoid collisions with ordinary real-world Rust, but ambiguous input is resolved consistently in favor of strutuct! rather than guessed from intent.

Generated-code lint guards

Generated declarations carry #[allow(dead_code)], because a complete algebraic hierarchy commonly contains types, fields, or variants that one consumer does not use. Generated constructor macros similarly carry #[allow(unused_macros)]. Every generated item already has synthetic documentation, so missing_docs does not need suppression. Every binding generated for an inclusion function is consumed, so unused_variables does not need suppression either. User attributes are emitted after the generated declaration guard, so a local #[deny(dead_code)] can opt a branch back into checking.

Option, box, attributes, and derives

Postfix T? and T* become Option<T> and Box<T>. Wrapped edges stop constructor-macro recursion, which makes T* useful for recursive families.

Ordinary root declaration attributes propagate to generated nested declarations, with one deliberate exception: documentation stays on exactly the declaration or field where it was written. This prevents one root comment from becoming the documentation for every generated subtype.

derive attributes are merged and deduplicated. cfg, cfg_attr, and third-party attributes are copied verbatim; strutuct! does not guess whether an arbitrary attribute supports every generated item shape, so that attribute or Rust remains responsible for diagnosing an incompatible target. Ordinary field and variant attributes remain local to those fields and variants. An attribute written inside a type position, such as field: #[some_attribute] Child { ... }, belongs to the generated ParentChild declaration and propagates through that generated branch. Conditional-compilation attributes also guard the declaration’s constructor macro, keeping cfg-exclusive families exclusive in both Rust’s type and macro namespaces.

A derive before an inline generated struct field adds traits for that declaration and every generated declaration below it:

You write Roughly expands to
strutuct! {
    #[derive(Debug, Clone, PartialEq, Eq)]
    Token
    #[derive(Copy, Hash)]
    kind: Kind { String, Integer },
}
#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)]
pub enum TokenKind {
    String,
    Integer,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Token {
    pub kind: TokenKind,
}

Here TokenKind retains the four inherited traits and additionally derives Copy and Hash. Repeated traits are deduplicated. For Default, select an enum’s default variant with Rust’s ordinary #[default] attribute.

#[underive(Trait, ...)] performs the opposite branch-local operation. It removes matching paths after inherited and local derives are merged, ignores paths that were absent, and propagates the reduced derive list further down:

You write Roughly expands to
strutuct! {
    #[derive(Debug, Clone, PartialEq)]
    Syntax
    #[underive(Clone)]
    node: Node {
        leaf: Leaf { Text(String) },
    },
}
#[derive(Debug, PartialEq)]
pub enum SyntaxNodeLeaf {
    Text(String),
}

#[derive(Debug, PartialEq)]
pub struct SyntaxNode {
    pub leaf: SyntaxNodeLeaf,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Syntax {
    pub node: SyntaxNode,
}

Put documentation and other root attributes inside the macro, immediately before the generated type’s name:

You write Roughly expands to
strutuct! {
    /// A literal token recognized by the parser.
    Literal
    String
    Integer
}
/// A literal token recognized by the parser.
pub enum Literal {
    String,
    Integer,
}

A doc comment written above a bare strutuct! belongs to the macro invocation itself. Put forward_attributes before the invocation’s other active attributes when those attributes should configure and propagate through the generated family.

Constructor paths

Generated enum macros consume nested path segments recursively. Conceptually:

You write Roughly expands to
A!(B::C::D(value))
A::B(AB::C(ABC::D(value)))

A generated struct or tuple ends the path and accepts its corresponding fields.

Enum inclusions

Set inclusions = true to generate the canonical consuming injection for every constructor occurrence in an enum tree:

You write Roughly expands to
strutuct! {
    #[strutuct(inclusions = true)]
    Token {
        Operator { Not(String), Plus },
        Keyword { Not(String) },
    }
}

let token = TokenOperatorNot("!".to_owned());
assert!(matches!(
    token,
    Token::Operator(TokenOperator::Not(value)) if value == "!"
));
pub enum TokenOperator {
    Not(String),
    Plus,
}

pub enum TokenKeyword {
    Not(String),
}

pub enum Token {
    Operator(TokenOperator),
    Keyword(TokenKeyword),
}

pub fn TokenOperator(value: TokenOperator) -> Token {
    Token::Operator(value)
}

pub fn TokenOperatorNot(value: String) -> Token {
    Token::Operator(TokenOperator::Not(value))
}

pub fn TokenOperatorPlus() -> Token {
    Token::Operator(TokenOperator::Plus)
}

pub fn TokenKeyword(value: TokenKeyword) -> Token {
    Token::Keyword(value)
}

pub fn TokenKeywordNot(value: String) -> Token {
    Token::Keyword(TokenKeyword::Not(value))
}

Names encode the complete constructor occurrence, not merely the endpoint type. Consequently, equal leaf types in distinct branches produce distinct functions such as TokenOperatorNot and TokenKeywordNot. Unit variants produce zero-argument functions; other inclusions consume their payload and return the root enum.

Joining proceeds only through nested enums. Products, generic containers, and postfix ? or * wrappers are boundaries; an enum below such a boundary keeps its own independently generated inclusions instead. A branch-local #[strutuct(inclusions = false)] stops that branch, while a local true can enable one branch when the surrounding declaration leaves the feature off.

For a chain of depth N, the macro emits N functions. Their composed bodies have total worst-case size O(N²); the macro never enumerates the possible factorizations of a path. Internally this is a bottom-up fold followed by the iterated join of homogeneous coproduct layers.

Tuple and unit structs also introduce value constructors. If an automatically named inclusion would occupy the same value-namespace name, strutuct! reports the collision. Give the payload a distinct exact name with Name |Payload| { ... }, disable inclusions for that branch, or use a non-tuple payload.

Ownership overloads of one operator

API reference on docs.rs: overload_op.

Rust resolves a + b, a + &b, and a += b through separate traits and separate impls, so one operator written once is not usable at the call sites that ordinary code actually contains. overload_op goes on the impl written for references and repeats it across the remaining forms.

It takes no arguments. The trait, both operand types, the result type and the method name are all already written in the impl it is attached to, which is emitted unchanged.

use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Vector(f64, f64);

#[overload_op]
impl Add<&Vector> for &Vector {
    type Output = Vector;

    fn add(self, rhs: &Vector) -> Vector {
        Vector(self.0 + rhs.0, self.1 + rhs.1)
    }
}

assert_eq!(&Vector(1.0, 2.0) + &Vector(3.0, 4.0), Vector(4.0, 6.0));
assert_eq!(Vector(1.0, 2.0) + &Vector(3.0, 4.0), Vector(4.0, 6.0));
assert_eq!(Vector(1.0, 2.0) + Vector(3.0, 4.0), Vector(4.0, 6.0));

let mut total = Vector(1.0, 2.0);
total += &Vector(3.0, 4.0);
total += Vector(1.0, 1.0);
assert_eq!(total, Vector(5.0, 7.0));

The reference impl is the one to write

Both operands must be shared references, and every generated impl delegates by borrowing. Nothing is cloned and no Clone bound is introduced. Writing the owned impl by hand instead would force the borrowed forms to clone, so that direction is rejected rather than supported:

use std::ops::Add;
struct Vector(f64);

// error: the operator must be implemented for a reference, as `impl Add<&T> for &T`
#[overload_op]
impl Add<&Vector> for Vector {
    type Output = Vector;

    fn add(self, rhs: &Vector) -> Vector {
        Vector(self.0 + rhs.0)
    }
}

An owned right operand is rejected the same way, and so is a &mut operand on either side, since the generated impls only ever lend their operands with &. Neither is assumed; both are checked, so a mistake is reported against the impl rather than showing up as a strange error inside generated code.

What is generated

From a binary operator, four impls follow:

GeneratedLeft operandRight operand
Trait<&Rhs> for Lhsownedborrowed
Trait<Rhs> for Lhsownedowned
TraitAssign<&Rhs> for Lhsmutatedborrowed
TraitAssign<Rhs> for Lhsmutatedowned

Spelled out for an addition, the generated impls are the following. Run it, or reveal the hidden lines to see the hand-written impl they all delegate to:

#![allow(unused)]
fn main() {
use std::ops::{Add, AddAssign};

#[derive(Debug, PartialEq)]
struct Vector(f64);

impl Add<&Vector> for &Vector {
    type Output = Vector;

    fn add(self, rhs: &Vector) -> Vector {
        Vector(self.0 + rhs.0)
    }
}

impl Add<&Vector> for Vector {
    type Output = Vector;

    fn add(self, rhs: &Vector) -> Vector {
        &self + rhs
    }
}

impl Add<Vector> for Vector {
    type Output = Vector;

    fn add(self, rhs: Vector) -> Vector {
        &self + &rhs
    }
}

impl AddAssign<&Vector> for Vector {
    fn add_assign(&mut self, rhs: &Vector) {
        *self = &*self + rhs;
    }
}

impl AddAssign<Vector> for Vector {
    fn add_assign(&mut self, rhs: Vector) {
        *self = &*self + &rhs;
    }
}

let mut total = Vector(1.0) + Vector(2.0);
total += &Vector(3.0);
println!("{total:?}");
}

Trait<Rhs> for &Lhs is deliberately absent: &a + b mixes a borrowed left operand with an owned right one, which is not a spelling ordinary code uses.

A unary operator generates the single owned form:

use std::ops::Neg;
#[derive(Debug, PartialEq)]
struct Vector(f64, f64);

#[overload_op]
impl Neg for &Vector {
    type Output = Vector;

    fn neg(self) -> Vector {
        Vector(-self.0, -self.1)
    }
}

assert_eq!(-&Vector(1.0, 2.0), Vector(-1.0, -2.0));
assert_eq!(-Vector(1.0, 2.0), Vector(-1.0, -2.0));

Unary and binary are told apart by the method’s own arity, so a trait naming no right operand still has one when its method takes an argument — Rhs then defaults to Self, which is the borrowed left operand:

use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Count(u32);

#[overload_op]
impl Add for &Count {
    type Output = Count;

    fn add(self, rhs: &Count) -> Count {
        Count(self.0 + rhs.0)
    }
}

assert_eq!(Count(1) + Count(2), Count(3));

Augmented assignment

a += b is generated as exactly a = &a + b. That is the standard case, and it is the only one the macro claims: a type whose in-place update is genuinely cheaper than rebuilding the value should write all four impls by hand rather than reach for the macro.

The assignment overloads are skipped when the result type differs from the left operand, because a = a op b is then not even well typed. An operator such as Point - Point -> Vector therefore generates only the two ownership overloads:

use std::ops::Sub;
#[derive(Debug, PartialEq)]
struct Vector(f64);

#[derive(Debug, PartialEq)]
struct Point(f64);

#[overload_op]
impl Sub<&Point> for &Point {
    type Output = Vector;

    fn sub(self, rhs: &Point) -> Vector {
        Vector(self.0 - rhs.0)
    }
}

assert_eq!(Point(5.0) - Point(2.0), Vector(3.0));

The comparison is made on the types as written, before any name is resolved, and a difference in leading qualifiers alone does not count: crate::Vector in one position and Vector in the other still generate the assignment. In case the two spellings turn out to name distinct types, each assignment impl is bounded by the operator itself returning the left operand type,

impl AddAssign<&Vector> for Vector
where
    for<'operand> &'operand Vector: Add<&'operand Vector, Output = Vector>,

so for distinct types the bound fails and the impl simply never applies, leaving the two ownership overloads, rather than failing to compile.

Any operator trait

Nothing here is specific to std::ops. The impl supplies the method name, so there is no table of operator names anywhere; only the augmented assignment is named by convention, suffixing the trait with Assign and the method with _assign:

trait Join<Rhs> {
    type Output;
    fn join(self, rhs: Rhs) -> Self::Output;
}

trait JoinAssign<Rhs> {
    fn join_assign(&mut self, rhs: Rhs);
}

#[derive(Debug, PartialEq)]
struct Path(String);

#[overload_op]
impl Join<&Path> for &Path {
    type Output = Path;

    fn join(self, rhs: &Path) -> Path {
        Path(format!("{}/{}", self.0, rhs.0))
    }
}

fn main() {
    let mut path = Path(String::from("usr"));
    path.join_assign(Path(String::from("local")));
    assert_eq!(path, Path(String::from("usr/local")));
}

Where the assignment trait is looked up depends on how the operator trait is written:

  • A path, such as std::ops::Add or crate::ops::Join: the assignment trait is the same path with the last segment suffixed, std::ops::AddAssign or crate::ops::JoinAssign.
  • A bare name, such as Add or Join: the suffixed name is looked up both in core::ops and among the names of the module surrounding the impl, so use std::ops::Add; alone is enough for the standard +=, and a custom JoinAssign is found beside a custom Join.

The two lookups of a bare name are equal, so a custom trait sharing a name with core::ops, such as a custom AddAssign imported beside a custom Add, makes that name ambiguous. Write the operator trait as a path, even just self::Add, to take the assignment trait from beside it instead.

The assignment trait is never found inside a function body, where items have no path and no module sees them, so declare custom operator and assignment traits at module scope, as above. Operand types are unaffected: the generated impls stay where the attributed impl is, so types declared in a function body work.

Generics and lifetimes

Generic parameters, bounds, and where predicates of the attributed impl are repeated on every generated impl. Lifetimes are the exception: one that named only an operand’s outer reference has nothing left to name once that reference is removed, so it is dropped from the generated header, and the generated bodies borrow with elided lifetimes instead.

A bound mentioning that lifetime is kept rather than dropped with it. The generated impls still need the bound, only for the fresh borrow their bodies create, so it becomes higher-ranked over the lifetime that went away. Writing

impl<'a, T> Add<&'a Pair<T>> for &'a Pair<T>
where
    &'a T: Add<&'a T, Output = T>,

therefore generates the owned impl under for<'a> &'a T: Add<&'a T, Output = T>, which is what its body requires. The explicit for<'b> spelling below states the same thing directly. An outlives bound such as T: 'a is the one exception: it constrained the lifetime being removed, and for<'a> T: 'a would demand 'static rather than what was written, so it is dropped with the lifetime.

use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Pair<T>(T, T);

#[overload_op]
impl<'a, T> Add<&'a Pair<T>> for &'a Pair<T>
where
    for<'b> &'b T: Add<&'b T, Output = T>,
{
    type Output = Pair<T>;

    fn add(self, rhs: &'a Pair<T>) -> Pair<T> {
        Pair(&self.0 + &rhs.0, &self.1 + &rhs.1)
    }
}

assert_eq!(Pair(1u32, 2u32) + Pair(3u32, 4u32), Pair(4u32, 6u32));

Operators that follow from the ones a type has

API reference on docs.rs: complete_ops.

Subtraction is not new information. If x + v makes sense, and v lies in an abelian group, then x - v makes sense too: -v lies in that same group, so x + (-v) is an addition the type already has. complete_ops writes that down, and claims nothing else.

use std::ops::{Add, Neg};
#[derive(Debug, PartialEq)]
#[complete_ops(Sub)]
struct Vector(f64);

#[overload_op]
impl Add<&Vector> for &Vector {
    type Output = Vector;

    fn add(self, rhs: &Vector) -> Vector {
        Vector(self.0 + rhs.0)
    }
}

#[overload_op]
impl Neg for &Vector {
    type Output = Vector;

    fn neg(self) -> Vector {
        Vector(-self.0)
    }
}

assert_eq!(Vector(5.0) - Vector(2.0), Vector(3.0));

let mut total = Vector(5.0);
total -= Vector(2.0);
assert_eq!(total, Vector(3.0));

The right operand stays generic

The completion says nothing about which type is subtracted, only that it is a group the attributed type can add:

impl<Rhs> Sub<Rhs> for Space
where
    Rhs: Neg<Output = Rhs>,
    Space: Add<Rhs>,
{
    type Output = <Space as Add<Rhs>>::Output;

    fn sub(self, rhs: Rhs) -> Self::Output {
        self + -rhs
    }
}

The completion is plain Rust, so it can be tried out directly. Edit the block below and run it, for example subtracting &Vector(2.0) to see a borrowed operand rejected:

use std::ops::{Add, Neg, Sub};

#[derive(Debug, Clone, Copy, PartialEq)]
struct Vector(f64);

impl Add for Vector {
    type Output = Vector;

    fn add(self, rhs: Vector) -> Vector {
        Vector(self.0 + rhs.0)
    }
}

impl Neg for Vector {
    type Output = Vector;

    fn neg(self) -> Vector {
        Vector(-self.0)
    }
}

// The impl `#[complete_ops(Sub)]` adds.
impl<Rhs> Sub<Rhs> for Vector
where
    Rhs: Neg<Output = Rhs>,
    Vector: Add<Rhs>,
{
    type Output = <Vector as Add<Rhs>>::Output;

    fn sub(self, rhs: Rhs) -> Self::Output {
        self + -rhs
    }
}

fn main() {
    println!("{:?}", Vector(5.0) - Vector(2.0));
}

Neg<Output = Rhs> is what makes the operand a group element rather than merely something negatable: an inverse stays in the group it came from. One impl therefore covers every group the type can add, rather than one impl per operand type, and the result type is whatever that addition already returns. Rhs and the lifetime of the borrowed operand are only default names: when the declaration already writes either, as a parameter, a bounded type, or its own name, the introduced one is numbered instead, as Rhs2. Nothing about the attributed type’s own structure is assumed, so a space acted on by a group completes exactly as a group acting on itself does:

use std::ops::{Add, Neg};
#[derive(Debug, PartialEq)]
struct Vector(f64);

#[derive(Debug, PartialEq)]
#[complete_ops(Sub)]
struct Point(f64);

#[overload_op]
impl Add<&Vector> for &Vector {
    type Output = Vector;

    fn add(self, rhs: &Vector) -> Vector {
        Vector(self.0 + rhs.0)
    }
}

#[overload_op]
impl Neg for &Vector {
    type Output = Vector;

    fn neg(self) -> Vector {
        Vector(-self.0)
    }
}

#[overload_op]
impl Add<&Vector> for &Point {
    type Output = Point;

    fn add(self, rhs: &Vector) -> Point {
        Point(self.0 + rhs.0)
    }
}

assert_eq!(Point(5.0) - Vector(2.0), Point(3.0));

Point has no addition of its own and no negation. It is only a space the group acts on, and translating it backwards is still x + (-v).

What it does not claim

The completion covers exactly the subtractions that are additions of an inverse, and the difference of two points of a space is not one of them. It is not merely a different operation: in general it does not exist. Take the circle acted on by the reals, t rotating a point by t. The action is transitive, but recovering a real number from two points of the circle needs a choice of branch of the logarithm, and no such choice follows from the action. So x - x' is never completed. Where a type does have one, it stays hand-written and coexists with the completion:

use std::ops::{Add, Neg, Sub};
#[derive(Debug, PartialEq)]
struct Vector(f64);

#[derive(Debug, PartialEq)]
#[complete_ops(Sub)]
struct Point(f64);
#[overload_op]
impl Add<&Vector> for &Vector {
    type Output = Vector;
    fn add(self, rhs: &Vector) -> Vector { Vector(self.0 + rhs.0) }
}
#[overload_op]
impl Neg for &Vector {
    type Output = Vector;
    fn neg(self) -> Vector { Vector(-self.0) }
}
#[overload_op]
impl Add<&Vector> for &Point {
    type Output = Point;
    fn add(self, rhs: &Vector) -> Point { Point(self.0 + rhs.0) }
}

impl Sub<&Point> for &Point {
    type Output = Vector;

    fn sub(self, rhs: &Point) -> Vector {
        Vector(self.0 - rhs.0)
    }
}

assert_eq!(&Point(5.0) - &Point(2.0), Vector(3.0));
assert_eq!(Point(5.0) - Vector(2.0), Point(3.0));

That coexistence rests on the space having no negation of its own: the two impls are disjoint because Point: Neg<Output = Point> cannot hold. A type that is its own group has a negation, so its completed Sub claims every right operand and no other Sub can be written for it. That is usually what you want — there is no second subtraction on a group — but it is a door that closes.

Completing the negation too

A type that multiplies by a scalar does not need a hand-written negation either, since -x is x scaled by -1. Naming the scalar completes Neg, which the subtraction then goes through:

use std::ops::{Add, Mul};
#[derive(Debug, PartialEq)]
#[complete_ops(Neg = -1.0f64, Sub)]
struct Scaled(f64);

#[overload_op]
impl Add<&Scaled> for &Scaled {
    type Output = Scaled;

    fn add(self, rhs: &Scaled) -> Scaled {
        Scaled(self.0 + rhs.0)
    }
}

#[overload_op]
impl Mul<&f64> for &Scaled {
    type Output = Scaled;

    fn mul(self, rhs: &f64) -> Scaled {
        Scaled(self.0 * rhs)
    }
}

assert_eq!(-Scaled(2.0), Scaled(-2.0));
assert_eq!(Scaled(5.0) - Scaled(2.0), Scaled(3.0));

The scalar is written as an expression, so its type cannot be named in the generated header and the completion states no bound for the multiplication it performs. On a concrete declaration, as above, that costs nothing. On a generic one the multiplication must already be available for every instantiation, which means carrying the bounds on the declaration itself:

// `Pair<T>` negates only where every `Pair<T>` can be scaled by the scalar.
#[complete_ops(Neg = -1.0f64)]
struct Pair<T: Copy + Mul<f64, Output = T>>(T, T);

Without them the expansion fails inside the generated neg, reported against the #[complete_ops(...)] attribute rather than against the declaration.

Suffix the literal, as -1.0f64 above, whenever the type has more than one scalar multiplication: the value is passed to Mul::mul as written, so its type is what selects the impl. The order inside the attribute does not matter, since a completed negation is always emitted ahead of the subtraction that uses it. Each operator may be named once; a repeated one is rejected rather than producing conflicting impls.

Borrowed operands

A borrowed right operand is not a group element: negating &Vector yields an owned Vector, so &Vector: Neg<Output = &Vector> does not hold and the completion does not apply. x - &v is therefore rejected:

error[E0271]: type mismatch resolving `<&Vector as Neg>::Output == &Vector`

Subtract the group element itself, as x - v.

The borrowed left operand fails for an unrelated reason. The completion does emit impl Sub<Rhs> for &Lhs, but reaching it needs Add<Rhs> for &Lhs — the ref-plus-owned combination overload_op deliberately does not generate — so that impl stays inert unless the addition is written by hand.

Compile-time stringification

API reference: stringify_as! on docs.rs.

stringify_as! turns one name or Rust type into a string literal at macro-expansion time; the option before ; selects the spelling. The output is an ordinary &'static str, so it works in constants, patterns, generated attributes, and other constant contexts without runtime allocation.

Name cases

Each case option accepts exactly one non-empty identifier or string literal. Keywords and raw identifiers are accepted as names; the raw r# marker is not part of the result.

const CAMEL: &str = stringify_as!(camel; some_HTTP_server);
const PASCAL: &str = stringify_as!(pascal; some_HTTP_server);
const SNAKE: &str = stringify_as!(snake; SomeHTTPServer);
const KEBAB: &str = stringify_as!(kebab; SomeHTTPServer);
const SHOUTING: &str = stringify_as!(screaming_snake; SomeHTTPServer);
const LOWER: &str = stringify_as!(lower; Some_HTTP_Server);
const UPPER: &str = stringify_as!(upper; Some_HTTP_Server);

assert_eq!(CAMEL, "someHttpServer");
assert_eq!(PASCAL, "SomeHttpServer");
assert_eq!(SNAKE, "some_http_server");
assert_eq!(KEBAB, "some-http-server");
assert_eq!(SHOUTING, "SOME_HTTP_SERVER");
assert_eq!(LOWER, "some_http_server");
assert_eq!(UPPER, "SOME_HTTP_SERVER");

The supported conversions are:

OptionResult style
camellowerCamelCase
pascalUpperCamelCase or PascalCase
snakesnake_case
kebabkebab-case
screaming_snakeSCREAMING_SNAKE_CASE
lowerUnicode lowercase; existing separators remain
upperUnicode uppercase; existing separators remain

A string literal contributes its contents rather than its Rust source spelling, so this also converts names that cannot be written as one identifier:

assert_eq!(stringify_as!(pascal; "some-http-server"), "SomeHttpServer");
assert_eq!(stringify_as!(upper; r#type), "TYPE");

The conventional word-based conversions use acronym and separator boundaries. The plain lower and upper options only change character case; they do not otherwise normalize the separators.

Rust types

The type option parses exactly one Rust type and emits a compact spelling with the prefix type::

const SIMPLE: &str = stringify_as!(type; String);
const BORROWED: &str = stringify_as!(type; &'static mut Vec<Option<String>>);
const FUNCTION: &str =
    stringify_as!(type; fn((u8, u16), *const [u8; 4]) -> bool);

assert_eq!(SIMPLE, "type:String");
assert_eq!(BORROWED, "type:&'static mut Vec<Option<String>>");
assert_eq!(FUNCTION, "type:fn((u8,u16),*const[u8;4])->bool");

Whitespace and source formatting disappear, while paths, lifetimes, pointer kinds, delimiters, and other type punctuation remain. The deliberately illegal identifier prefix keeps a type name such as type:String distinct from an ordinary identifier-derived name.

This is canonicalization of written Rust type syntax, not compiler type identity. It does not resolve aliases, imports, or whether two differently written types happen to denote the same semantic type.

qf!

API reference on docs.rs: qf!.

qf! recursively qualifies common unqualified standard-library types. It is mainly useful in generated code, where relying on imports from the caller would be impolite.

You write Roughly expands to
type Messages = qf!(Option<Vec<String>>);

let messages: Messages = Some(vec![String::from("hello")]);
assert_eq!(messages.as_ref().map(Vec::len), Some(1));
type Messages = ::core::option::Option<
    ::std::vec::Vec<::std::string::String>,
>;

let messages: Messages = Some(vec![String::from("hello")]);
assert_eq!(messages.as_ref().map(Vec::len), Some(1));

The recognized names are:

  • String, Box, and Vec;
  • HashMap, HashSet, BTreeMap, and BTreeSet;
  • Option and Arc.

Only single-segment paths are rewritten. A deliberately qualified path such as application::String is preserved, because writing the qualifier is how the caller says that this is not std::string::String.

shared_match_arms!

API reference on docs.rs: shared_match_arms!.

Rust or-patterns require every repeated binding to have the same concrete type. shared_match_arms! instead clones one RHS into several ordinary match arms, so each binding is type-checked independently:

You write Roughly expands to
enum Value {
    Number(u32),
    Character(char),
}

let value = Value::Number(3);
let text = shared_match_arms! {
    match value {
        Value::Number(value) || Value::Character(value) => value.to_string(),
    }
};

assert_eq!(text, "3");
enum Value {
    Number(u32),
    Character(char),
}

let value = Value::Number(3);
let text = match value {
    Value::Number(value) => value.to_string(),
    Value::Character(value) => value.to_string(),
};

assert_eq!(text, "3");

The invented || joins complete arm patterns, not Rust or-patterns. The expansion above contains two independent arms with the same RHS.

Guards

Parenthesize a component when it has its own guard:

You write Roughly expands to
enum Value {
    Number(u32),
    Character(char),
}

let value = Value::Number(3);
let selected = shared_match_arms! {
    match value {
        (Value::Number(value) if value > 0)
            || (Value::Character(value) if value.is_ascii())
            => true,
        _ => false,
    }
};

assert!(selected);
enum Value {
    Number(u32),
    Character(char),
}

let value = Value::Number(3);
let selected = match value {
    Value::Number(value) if value > 0 => true,
    Value::Character(value) if value.is_ascii() => true,
    _ => false,
};

assert!(selected);

Logical OR remains ordinary Rust when it occurs inside a component guard:

(Pattern(value) if first(value) || second(value)) || Other(value) => rhs

Whole-stream rewriting

Like literally_literal_string!, this macro accepts an arbitrary token stream and searches recursively through its groups. Attributes stay opaque, and excluded_macros can protect macro inputs with private syntax. It can also be listed in expand! when the extended syntax lives in an out-of-line module.

This is deliberately a function-like macro. An attribute macro would require rustc to accept the surrounding item before this expression-level extension could reliably rewrite it.

Meta transformers

API reference on docs.rs: reflect!, perm!.

Meta transformers operate on invocation objects and token-stream structure rather than assuming that their input is a Rust item or expression.

Reflecting invocations

reflect! accepts two macro paths and an opaque body:

macro_rules! add_one {
    ($expression:expr) => { 1 + $expression };
}

macro_rules! double {
    ($expression:expr) => { 2 * $expression };
}

let reflected = reflect!(add_one, double; 3);
assert_eq!(reflected, 8);

It performs only the structural reflection

first! { second! { body } }
    ↦ second! { first! { body } }

so the example becomes double! { add_one! { 3 } }. The inner body is never parsed by reflect!, and both macro paths retain their call-site spans. This is not an API for forcing another macro to expand inside the current procedural macro; it constructs the reflected invocation objects and lets rustc expand them normally afterward.

Internally the implementation is exactly the composition

invoke(second, invoke(first, body))

where invoke pairs one macro path with one opaque token stream.

Outer attributes may precede either path and move together with that invocation:

reflect!(#[a] first, #[b] second; body)
    -> #[b] second! { #[a] first! { body } }

Whether a particular attribute is valid still depends on the Rust context in which the resulting invocation appears. The input describes invocation paths rather than parsing two complete existing macro calls, so reflect! always constructs braced invocations and does not preserve operand delimiters or trailing semicolons. An outer attribute written before reflect! itself belongs to the reflect! call, not either operand.

Permuting token trees

perm! uses ordinary one-based cycle notation. Separate positions with spaces: 14 is one integer token, whereas 1 4 denotes two positions.

perm! { ((1 4 3)), a, b, c, d, e }
    -> c, b, d, a, e

Cycles compose from right to left. The permutation acts by moving the token tree at position i to position σ(i). Positions beyond the largest index appearing in any cycle remain fixed, so the same notation implicitly embeds S_n into every S_{n+k}.

The implementation stores the destination of each source position only through the final non-fixed point. Therefore the identity needs no stored entries, a cycle constructs one finite map, and multiplication is ordinary function composition:

(sigma * tau)(i) = sigma(tau(i))

This representation is also what applies a written cycle product to the token trees, so the algebra exercised by the implementation tests and the documented right-to-left expansion rule are the same code path.

Each comma-delimited operand must be exactly one token tree: an identifier, literal, punctuation token, or one delimited group. Put a multi-token operand in a group if it should move as one unit.

The expansion is the raw comma-separated stream, deliberately without a tuple, array, block, or other imposed Rust shape. This makes perm! a structural building block, but its invocation must ultimately occur in a context whose consumer accepts that output shape.

Run the two complete programs shipped with the crate using cargo run --example meta_transformers and cargo run --example enum_fn.

forward_attributes

API reference on docs.rs: forward_attributes.

forward_attributes turns ordinary outer attributes into an argument envelope for any item-position function-like macro. The receiving macro decides what the attributes mean.

You write Roughly expands to
#[forward_attributes]
#[derive(Debug, PartialEq)]
strutuct! {
    State { Ready, Waiting }
}

fn main() {
    assert_eq!(State::Ready, State::Ready);
    assert_eq!(format!("{:?}", State::Waiting), "Waiting");
}
strutuct! {
    #[derive(Debug, PartialEq)]
    ;
    State { Ready, Waiting }
}

fn main() {
    assert_eq!(State::Ready, State::Ready);
    assert_eq!(format!("{:?}", State::Waiting), "Waiting");
}

The semicolon distinguishes invocation-wide attributes from attributes that were already attached to the first object in the macro input. strutuct! and emmun! interpret the left side as inherited attributes and configuration; local attributes on the right retain their branch-specific meaning.

Why the invocation is enough

A function-like macro invocation in item position is already an attribute-compatible item shell. forward_attributes therefore does not need to invent a dummy struct: it parses only that shell, removes its outer attributes, and puts them at the front of the invocation’s existing token group. Rust still leaves the receiving macro’s private contents opaque.

This is specifically an item-position bridge. It does not make a macro call in type, pattern, or nested expression position into a target for an attribute procedural macro.

Expansion order

forward_attributes must be the first active attribute. An active attribute written before it may expand or fail before forwarding gets a turn:

You write What Rust sees first
#[derive(Debug)]
#[forward_attributes]
strutuct! { TooLate { A, B } }

fn main() {}
// `derive` runs before `forward_attributes`
// can move it into the macro input.
compile_error!(
    "derive may only be applied to structs, enums and unions"
);

Inert attributes such as documentation may precede it and are forwarded too.

Opaque input

Only the function-like invocation shell is parsed. Its delimited input remains an opaque token stream and may use any private syntax accepted by the receiving macro. The input must still be tokenizable and have balanced delimiters.

Macros opt into this convention by parsing:

zero or more outer attributes ; ordinary macro input

There is deliberately no universal meaning attached to those attributes.

Literal syntax and external modules

API reference on docs.rs: literally_literal_string!, expand!, excluded_macros.

literally_literal_string!

literally_literal_string! turns @@"text" into an owned String:

You write Roughly expands to
let greeting: String = literally_literal_string!(@@"hello");
assert_eq!(greeting, "hello");
let greeting: String = ::std::string::String::from("hello");
assert_eq!(greeting, "hello");

That spelling is not valid ordinary Rust. It works inside a function-like macro because rustc gives the macro its input tokens before parsing them as an expression.

expand!

At item level, invalid syntax must live in a separate file so rustc does not parse it first. expand! loads an out-of-line module and wraps its body in the selected rewriting macros.

You write Roughly expands to
expand!(
    literally_literal_string;
    mod experiments;
);

// experiments.rs
pub fn greeting() -> String {
    @@"hello from inadvisable Rust"
}
mod experiments {
    pub fn greeting() -> String {
        ::std::string::String::from(
            "hello from inadvisable Rust",
        )
    }
}

Multiple macro paths are applied in their written order. expand! currently loads one module file; it does not recursively load out-of-line child modules. Cargo may also miss a change made only to a file read by the procedural macro, so force a rebuild if an edit appears to be ignored.

Excluding macro inputs

Some macros own private token syntax that another transformation must not touch. Configure exact macro names in expand!:

You write Roughly expands to
expand!(
    literally_literal_string,
    exclude_macros = (raw_tokens);
    mod experiments;
);

// experiments.rs
raw_tokens!(@@"untouched");
let text = @@"rewritten";
mod experiments {
    raw_tokens!(@@"untouched");
    let text = ::std::string::String::from("rewritten");
}

The excluded_macros attribute provides the same configuration when wrapping a configuration-aware item-position transformation.

How token rewriting behaves

The syntax-rewriting macros operate structurally on token trees rather than by editing source strings.

Groups are recursive

Every ordinary parenthesized, braced, and bracketed token group is visited recursively. A transformation therefore works at any nesting depth without assuming that its input is a Rust item, expression, type, or even valid Rust syntax yet.

Attributes are opaque

Outer and inner attributes are copied as complete opaque regions. Tokens inside #[...] and #![...] are never rewritten. This keeps derive helpers, cfg expressions, and other attribute-specific syntax under the control of the attribute that owns them.

Excluded macros are opaque

An excluded invocation is copied together with its complete delimited input. Exclusion is exact, including raw identifiers. Declarative macro definitions can also be excluded without rewriting their bodies.

Macro exclusions are shared through a private configuration envelope when several rewriting macros are chained by expand!.

What this does not promise

Token rewriting does not make arbitrary text valid Rust. The final transformed stream still has to parse and type-check normally. Diagnostics after a large transformation may also point at the macro invocation rather than at an exact character in the out-of-line source.