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.