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_strassigns each variant a unique string and generates a literal-selected delegating constructor.enum_fngenerates a method from inline per-variant match-arm expressions.callableandmake_fn!give a local object function-like syntax without erasing its type or its ordinary methods.strutuct!, withemmun!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_attributesmakes 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!andexpand!let token rewriting handle syntax that Rust cannot parse as an ordinary source file.reflect!andperm!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:
callableandmake_fn!give objects function-like macro syntax while retaining their original types and methods;discriminated_strcreates a unique string mapping and a literal-selected constructor macro;enum_fnturns per-variant expressions into a method;strutuct!, also exported asemmun!, generates related structs, enums, and constructor macros;overload_oprepeats one operator written for references across its owned operand forms and its augmented assignment;complete_opsadds 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:
qf!recursively qualifies common paths in one Rust type;stringify_as!converts one identifier or string literal to a selected conventional case, or emits the compact, structure-preserving string name of one Rust type.
Recursive whole-stream extensions
These macros rewrite arbitrary token trees recursively without assuming that the complete input is an item or expression:
shared_match_arms!duplicates one match-arm RHS across independently typed patterns;literally_literal_string!recognizes the deliberately invalid@@"text".
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_attributesmoves an item-position macro invocation’s outer attributes behind a;boundary in its opaque input;excluded_macrosmarks 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.
#[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:
#[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:
#[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:
#[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:
#[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:
#[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:
#[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:
#[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:
#[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.
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:
#[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);
}
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:
- A leading
name: Typemakes the complete body a named-field struct. - Otherwise, one complete
(A, B, ...)body with at least two types makes a tuple struct. - 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: Typemembers 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:
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:
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:
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:
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:
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:
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 | falsegenerates consuming functions for enum constructor paths; it is disabled by default;product_variants = true | falseselects packed products versus ordinary multi-field enum variants;public = true | falseselects the default visibility for that declaration branch;falserestores Rust’s ordinary private-by-default behavior;reverse_concat = true | falsereverses every automatically concatenated name in that branch. For example,ParentNamebecomesNameParent, 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:
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:
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:
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:
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:
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:
| Generated | Left operand | Right operand |
|---|---|---|
Trait<&Rhs> for Lhs | owned | borrowed |
Trait<Rhs> for Lhs | owned | owned |
TraitAssign<&Rhs> for Lhs | mutated | borrowed |
TraitAssign<Rhs> for Lhs | mutated | owned |
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::Addorcrate::ops::Join: the assignment trait is the same path with the last segment suffixed,std::ops::AddAssignorcrate::ops::JoinAssign. - A bare name, such as
AddorJoin: the suffixed name is looked up both incore::opsand among the names of the module surrounding the impl, souse std::ops::Add;alone is enough for the standard+=, and a customJoinAssignis found beside a customJoin.
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:
| Option | Result style |
|---|---|
camel | lowerCamelCase |
pascal | UpperCamelCase or PascalCase |
snake | snake_case |
kebab | kebab-case |
screaming_snake | SCREAMING_SNAKE_CASE |
lower | Unicode lowercase; existing separators remain |
upper | Unicode 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.
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, andVec;HashMap,HashSet,BTreeMap, andBTreeSet;OptionandArc.
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:
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:
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.
#[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:
#[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:
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.
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!:
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.