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

moxy

Introduction

Moxy is a Rust derive macro crate that eliminates boilerplate. Get Display, Deref, Default, Build, Get, Set, Union, and Forward implementations with a single attribute — no hand-written impl blocks needed.

What You Get

  • Display — Flexible std::fmt::Display with multiple output formats, JSON serialization, and colored terminal output.
  • Deref — Automatic std::ops::Deref delegation to inner fields.
  • Build — Type-safe fluent builder with compile-time field tracking, V: Into<T> setters, and inline defaults.
  • Defaultstd::default::Default with per-field custom expressions via #[moxy(default = expr)].
  • Get — Field getters returning Deref::Target (e.g. String&str), with copy/clone/mutable modifiers and callbacks.
  • Set — Field setters with Into<T> coercion, Option<T> auto-wrapping, transform callbacks, and chaining.
  • Union — Enum variant accessors (is_*, as_*, to_*), common field detection, and the unionize! shorthand macro.
  • Forward — Forward method calls to inner fields (structs) or variant payloads (enums) by declaring method signatures.

Quick Example

use moxy::{Deref, Display};

#[derive(Deref, Display)]
#[moxy(display(debug, pretty))]
struct User {
    #[moxy(deref)]
    name: String,
    email: String,
}

let user = User {
    name: "John".into(),
    email: "john@example.com".into(),
};

// Display output:
// User {
//     name: "John",
//     email: "john@example.com",
// }

// Deref delegates to name:
assert_eq!(user.len(), 4);

Inspiration

Moxy draws ideas from these excellent crates:

License

MIT

Getting Started

Installation

Add moxy to your Cargo.toml:

[dependencies]
moxy = "0.0.4"

To enable all optional features:

[dependencies]
moxy = { version = "0.0.4", features = ["full"] }

Tip

Enable full during development to get access to all features, then trim to only what you need before publishing.

Or pick individual features:

[dependencies]
moxy = { version = "0.0.4", features = ["json", "color"] }

See Feature Flags for details on each feature.

Basic Usage

Import the derives you need from moxy:

use moxy::{Default, Deref, Display};

Display

Add #[derive(Display)] to get a std::fmt::Display implementation:

use moxy::Display;

#[derive(Display)]
struct User {
    name: String,
    email: String,
}

let user = User {
    name: "John".into(),
    email: "john@example.com".into(),
};

println!("{user}");
// User { name: John, email: john@example.com }

Deref

Add #[derive(Deref)] to delegate std::ops::Deref to an inner field:

use moxy::Deref;

#[derive(Deref)]
struct Email(String);

let email = Email("john@example.com".into());
assert_eq!(email.len(), 16);

Build

Add #[derive(Build)] to generate a fluent builder. Annotate each field you want in the builder with #[moxy(build)]:

use moxy::Build;

#[derive(Build, Default)]
struct Config {
    #[moxy(build)]
    pub host: String,
    #[moxy(build(default = 8080u16))]
    pub port: u16,
}

let config = Config::new().host("localhost").build();
assert_eq!(config.host, "localhost");
assert_eq!(config.port, 8080);

Default

Note

use moxy::Default shadows Rust’s built-in Default derive. If you need both in the same module, qualify the std one as #[derive(std::default::Default)].

Add #[derive(Default)] and annotate fields with #[moxy(default = expr)] to generate a custom Default implementation:

use moxy::Default;

#[derive(Default)]
struct Config {
    #[moxy(default = "localhost")]
    pub host: String,
    #[moxy(default = 8080u16)]
    pub port: u16,
    pub debug: bool,
}

let config = Config::default();
assert_eq!(config.host, "localhost");
assert_eq!(config.port, 8080);
assert_eq!(config.debug, false);

Get

Add #[derive(Get)] and annotate fields with #[moxy(get)] to generate getters:

use moxy::Get;

#[derive(Get)]
struct User {
    #[moxy(get)]
    name: String,
    #[moxy(get)]
    bio: Option<String>,
}

let user = User { name: "alice".into(), bio: Some("hello".into()) };
assert_eq!(user.name(), "alice");
assert_eq!(user.bio(), Some("hello"));

Set

Add #[derive(Set)] and annotate fields with #[moxy(set)] to generate setters with Into<T> coercion:

use moxy::Set;

#[derive(Set)]
struct Config {
    #[moxy(set)]
    host: String,
    #[moxy(set)]
    port: u16,
}

let mut cfg = Config { host: String::new(), port: 0 };
cfg.set_host("localhost").set_port(8080_u16);
assert_eq!(cfg.host, "localhost");

Union

Add #[derive(Union)] to an enum to generate is_*(), as_*(), and to_*() variant accessors:

use moxy::Union;

#[derive(Union)]
enum Value {
    Text(String),
    Number(i32),
    Empty,
}

let v = Value::Text("hello".into());
assert!(v.is_text());
assert_eq!(v.as_text(), Some(&"hello".to_string()));
assert_eq!(v.to_text(), Some("hello".to_string()));
assert!(!v.is_number());

unionize!

Use unionize! to generate a union-type enum with variant methods and From impls in one step:

moxy::unionize! {{
    Text => String,
    Number => i32,
    Flag => bool,
} as pub Value}

let v: Value = "hello".to_string().into();
assert!(v.is_text());

Forward

Add #[derive(Forward)] and declare method signatures on fields to generate forwarding methods:

use moxy::Forward;

#[derive(Forward)]
struct Wrapper {
    #[moxy(forward(
        fn len(&self) -> usize,
        fn is_empty(&self) -> bool,
    ))]
    inner: Vec<String>,
}

let w = Wrapper { inner: vec!["hello".into()] };
assert_eq!(w.len(), 1);
assert!(!w.is_empty());

Next Steps

Display

The Display derive macro implements std::fmt::Display for your structs. It supports multiple output formats, custom format strings, JSON serialization, and colored terminal output.

Default Format

Without any attributes, Display produces a struct-literal style output:

use moxy::Display;

#[derive(Display)]
struct User {
    name: String,
    email: String,
}

let user = User {
    name: "John".into(),
    email: "john@example.com".into(),
};

assert_eq!(format!("{user}"), "User { name: John, email: john@example.com }");

Attribute Syntax

Display behavior is controlled through #[moxy(display(...))] attributes at the struct level and field level:

use moxy::Display;

#[derive(Display)]
#[moxy(display(debug, pretty))]      // struct-level: format + modifiers
struct User {
    name: String,
    #[moxy(display(skip))]            // field-level: skip this field
    password: String,
}

Multiple #[moxy(...)] attributes on the same item are merged, so these two forms are equivalent:

#[moxy(display(debug, pretty))]

// same as:
#[moxy(display(debug))]
#[moxy(display(pretty))]

Tip

Splitting attributes across multiple lines is useful when mixing format flags with field-level overrides — each concern can live on its own #[moxy(...)] line.

What’s Next

Formats

The Display derive supports several built-in format modes. Each is specified as a flag in the #[moxy(display(...))] attribute.

Note

Format flags are mutually exclusive — only one of debug, compact, keyvalue, map, or json can be active at a time. Modifiers like pretty and color can be combined with any format.

Default

No attribute needed. Produces struct-literal style output with the type name, field names, and unquoted values:

use moxy::Display;

#[derive(Display)]
struct User {
    name: String,
    email: String,
}

// User { name: John, email: john@example.com }

Debug

Wraps string values in quotes, similar to Rust’s Debug trait:

use moxy::Display;

#[derive(Display)]
#[moxy(display(debug))]
struct User {
    name: String,
    email: String,
}

// User { name: "John", email: "john@example.com" }

Compact

Values only, space-separated. No type name, no field names, no punctuation:

use moxy::Display;

#[derive(Display)]
#[moxy(display(compact))]
struct User {
    name: String,
    email: String,
}

// John john@example.com

Key-Value

Field-value pairs separated by =, space-delimited:

use moxy::Display;

#[derive(Display)]
#[moxy(display(keyvalue))]
struct User {
    name: String,
    email: String,
}

// name=John email=john@example.com

Map

Anonymous map style — like default but without the type name:

use moxy::Display;

#[derive(Display)]
#[moxy(display(map))]
struct User {
    name: String,
    email: String,
}

// { name: John, email: john@example.com }

Pretty Printing

Add pretty to any format for multi-line output with indentation. It works as a modifier — combine it with default, debug, keyvalue, map, and json formats. Compact mode does not support pretty printing.

Default + Pretty

use moxy::Display;

#[derive(Display)]
#[moxy(display(pretty))]
struct User {
    name: String,
    email: String,
}

let user = User {
    name: "John".into(),
    email: "john@example.com".into(),
};

// User {
//     name: John,
//     email: john@example.com,
// }

Debug + Pretty

use moxy::Display;

#[derive(Display)]
#[moxy(display(debug, pretty))]
struct User {
    name: String,
    email: String,
}

// User {
//     name: "John",
//     email: "john@example.com",
// }

Key-Value + Pretty

One pair per line, no indentation:

use moxy::Display;

#[derive(Display)]
#[moxy(display(keyvalue, pretty))]
struct User {
    name: String,
    email: String,
}

// name=John
// email=john@example.com

Map + Pretty

use moxy::Display;

#[derive(Display)]
#[moxy(display(map, pretty))]
struct User {
    name: String,
    email: String,
}

// {
//     name: John,
//     email: john@example.com,
// }

Tuple Structs

Pretty printing works with tuple structs too:

use moxy::Display;

#[derive(Display)]
#[moxy(display(pretty))]
struct Pair(String, i32);

// Pair(
//     hello,
//     42,
// )
use moxy::Display;

#[derive(Display)]
#[moxy(display(debug, pretty))]
struct Pair(String, i32);

// Pair(
//     "hello",
//     42,
// )

Custom Format Strings

Instead of using a built-in format, you can provide a custom format string. Field names are interpolated directly using {field_name} syntax.

Basic Interpolation

Reference fields by name inside curly braces:

use moxy::Display;

#[derive(Display)]
#[moxy(display("hi! my name is {name} and my email is {email}"))]
struct User {
    name: String,
    email: String,
}

let user = User {
    name: "John".into(),
    email: "john@example.com".into(),
};

assert_eq!(
    format!("{user}"),
    "hi! my name is John and my email is john@example.com"
);

Expression Arguments

Use std::fmt-style positional arguments with arbitrary Rust expressions. The format string comes first, followed by comma-separated expressions:

#[moxy(display("{}", expr))]

Self Field Access

Access fields through self:

use moxy::Display;

#[derive(Display)]
#[moxy(display("{}", self.name))]
struct User {
    name: String,
}

// John

Self Method Calls

Call methods on self:

use moxy::Display;

#[derive(Display)]
#[moxy(display("{}", self.greeting()))]
struct User {
    name: String,
}

impl User {
    fn greeting(&self) -> String {
        format!("Hello, {}!", self.name)
    }
}

// Hello, John!

Arithmetic and Other Expressions

Any valid Rust expression works:

use moxy::Display;

#[derive(Display)]
#[moxy(display("double: {}", count * 2))]
struct Counter {
    count: i32,
}

let counter = Counter { count: 5 };
assert_eq!(format!("{counter}"), "double: 10");

JSON

The json format serializes your struct to JSON using serde_json. This requires the json feature flag.

Setup

Important

The json feature flag must be enabled and the struct must also derive serde::Serialize. Without both, the json format flag will not compile.

Enable the json feature in your Cargo.toml:

[dependencies]
moxy = { version = "0.0.4", features = ["json"] }
serde = { version = "1", features = ["derive"] }

Your struct must derive both Display and serde::Serialize:

use moxy::Display;

#[derive(Display, serde::Serialize)]
#[moxy(display(json))]
struct User {
    name: String,
    age: i32,
}

Named Structs

Named structs produce JSON objects:

#[derive(Display, serde::Serialize)]
#[moxy(display(json))]
struct User {
    name: String,
    age: i32,
}

let user = User { name: "John".into(), age: 30 };
// {"age":30,"name":"John"}

Tuple Structs

Tuple structs produce JSON arrays:

#[derive(Display, serde::Serialize)]
#[moxy(display(json))]
struct Pair(String, i32);

let pair = Pair("hello".into(), 42);
// ["hello",42]

Pretty JSON

Combine with pretty for indented output:

#[derive(Display, serde::Serialize)]
#[moxy(display(json, pretty))]
struct User {
    name: String,
    age: i32,
}

let user = User { name: "John".into(), age: 30 };
// {
//   "age": 30,
//   "name": "John"
// }

Skipping Fields

Use #[moxy(display(skip))] to exclude fields from JSON output:

#[derive(Display, serde::Serialize)]
#[moxy(display(json))]
struct User {
    name: String,
    #[moxy(display(skip))]
    secret: String,
}

let user = User { name: "John".into(), secret: "hunter2".into() };
// {"name":"John"}

Field Aliases

Field aliases are applied to JSON keys:

#[derive(Display, serde::Serialize)]
#[moxy(display(json))]
struct User {
    #[moxy(display(alias = "full_name"))]
    name: String,
}

let user = User { name: "John".into() };
// {"full_name":"John"}

Color

The color modifier adds ANSI truecolor (24-bit RGB) output via the colored crate. It works as a modifier — combine it with any display format.

Setup

Important

The color feature flag must be enabled. Without it, the color modifier is not available.

Enable the color feature in your Cargo.toml:

[dependencies]
moxy = { version = "0.0.4", features = ["color"] }

Basic Usage

Use color for the default theme (Dracula):

use moxy::Display;

#[derive(Display)]
#[moxy(display(color))]
struct User {
    name: String,
    email: String,
}

Themes

Specify a theme by name:

#[derive(Display)]
#[moxy(display(color = "dracula"))]
struct User { name: String, email: String }

#[derive(Display)]
#[moxy(display(color = "atom-one-dark"))]
struct Config { host: String, port: u16 }

#[derive(Display)]
#[moxy(display(color = "github-dark"))]
struct Status { code: u16, message: String }

Theme Colors

Each theme colorizes four elements: the struct name, field names, values, and punctuation (braces, colons, commas).

ThemeStruct NameField NamesValuesPunctuation
dracula (default)cyanpinkyellowwhite
atom-one-darkgoldpurplegreengray
github-darkblueredlight bluelight gray

Combining with Formats

Color works with default, debug, map, and keyvalue formats. Compact and JSON modes do not support color.

Note

compact and json formats ignore the color modifier — ANSI codes are not injected for these modes.

// Default + color
#[moxy(display(color))]

// Debug + color
#[moxy(display(debug, color))]

// Map + color
#[moxy(display(map, color))]

// Key-value + color with a specific theme
#[moxy(display(keyvalue, color = "github-dark"))]

// Color + pretty
#[moxy(display(color, pretty))]

// Debug + color + pretty
#[moxy(display(debug, color, pretty))]

Tuple Structs

Color works with tuple structs:

#[derive(Display)]
#[moxy(display(color))]
struct Pair(String, i32);

Fields

Field-level attributes let you control which fields appear in the output and how they’re labeled.

Skip

Exclude a field from the display output with #[moxy(display(skip))]:

use moxy::Display;

#[derive(Display)]
struct User {
    name: String,
    #[moxy(display(skip))]
    email: String,
}

let user = User {
    name: "John".into(),
    email: "john@example.com".into(),
};

assert_eq!(format!("{user}"), "User { name: John }");

Skip works with all formats — the field is omitted from the output regardless of the display mode.

Aliases

Field Alias

Rename a field in the output with #[moxy(display(alias = "..."))]:

use moxy::Display;

#[derive(Display)]
struct User {
    #[moxy(display(alias = "full_name"))]
    name: String,
    email: String,
}

let user = User {
    name: "John".into(),
    email: "john@example.com".into(),
};

assert_eq!(
    format!("{user}"),
    "User { full_name: John, email: john@example.com }"
);

Struct Alias

Rename the struct itself in the output with a struct-level alias:

use moxy::Display;

#[derive(Display)]
#[moxy(display(alias = "Person"))]
struct User {
    name: String,
    email: String,
}

let user = User {
    name: "John".into(),
    email: "john@example.com".into(),
};

assert_eq!(
    format!("{user}"),
    "Person { name: John, email: john@example.com }"
);

Combining Aliases

Struct and field aliases can be used together, and they work with any format:

use moxy::Display;

#[derive(Display)]
#[moxy(display(debug, alias = "U"))]
struct User {
    #[moxy(display(alias = "n"))]
    name: String,
    email: String,
}

let user = User {
    name: "John".into(),
    email: "john@example.com".into(),
};

assert_eq!(
    format!("{user}"),
    "U { n: \"John\", email: \"john@example.com\" }"
);

Struct Types

The Display derive works with named structs, tuple structs, and unit structs. Each struct type has its own output style.

Named Structs

The most common case. Fields are displayed with their names:

use moxy::Display;

#[derive(Display)]
struct User {
    name: String,
    email: String,
}

// User { name: John, email: john@example.com }

All format modes, pretty printing, field attributes, and modifiers work with named structs.

Tuple Structs

Tuple struct fields are displayed positionally inside parentheses:

use moxy::Display;

#[derive(Display)]
struct Pair(String, i32);

let pair = Pair("hello".into(), 42);
// Pair(hello, 42)

With Debug

use moxy::Display;

#[derive(Display)]
#[moxy(display(debug))]
struct Pair(String, i32);

// Pair("hello", 42)

With Compact

use moxy::Display;

#[derive(Display)]
#[moxy(display(compact))]
struct Pair(String, i32);

// hello 42

With Pretty

use moxy::Display;

#[derive(Display)]
#[moxy(display(pretty))]
struct Pair(String, i32);

// Pair(
//     hello,
//     42,
// )

Unit Structs

Unit structs display as just their type name:

use moxy::Display;

#[derive(Display)]
struct Marker;

assert_eq!(format!("{}", Marker), "Marker");

Enums

The Display derive works with enums. Each variant is rendered independently based on its kind (unit, tuple, named).

Default Style

use moxy::Display;

#[derive(Display)]
enum Shape {
    Circle(f64),
    Rect { width: f64, height: f64 },
    Point,
}

assert_eq!(Shape::Circle(3.14).to_string(), "Circle(3.14)");
assert_eq!(Shape::Rect { width: 4.0, height: 2.0 }.to_string(), "Rect { width: 4, height: 2 }");
assert_eq!(Shape::Point.to_string(), "Point");

Enum-Level Style

Apply a style to all variants at once:

use moxy::Display;

#[derive(Display)]
#[moxy(display(debug))]
enum Value {
    Text(String),
    Num(i32),
}

assert_eq!(Value::Text("hi".into()).to_string(), "Text(\"hi\")");
assert_eq!(Value::Num(42).to_string(), "Num(42)");

Per-Variant Overrides

Variant-level #[moxy(display(...))] overrides the enum-level style:

use moxy::Display;

#[derive(Display)]
#[moxy(display(debug))]
enum Token {
    #[moxy(display(compact))]
    Ident(String),
    Literal { value: String, kind: String },
}

// Ident uses compact (override), Literal uses debug (enum default)

Custom Format Strings

use moxy::Display;

#[derive(Display)]
enum Expr {
    #[moxy(display("{x}x{y}"))]
    Rect { x: i32, y: i32 },
    Point,
}

assert_eq!(Expr::Rect { x: 4, y: 2 }.to_string(), "4x2");
assert_eq!(Expr::Point.to_string(), "Point");

Field Attributes

skip and alias work within named variants:

use moxy::Display;

#[derive(Display)]
enum Entry {
    User {
        name: String,
        #[moxy(display(skip))]
        password: String,
    },
}

All struct-level styles work per-variant: debug, compact, keyvalue, map, json, color, pretty, alias.

Deref

The Deref derive macro implements std::ops::Deref, forwarding to an inner field. This is useful for the newtype pattern — wrapping a type while exposing its methods directly.

Single-Field Structs

For structs with one field, Deref automatically targets that field. No attribute needed.

Tuple Struct

use moxy::Deref;

#[derive(Deref)]
struct Email(String);

let email = Email("john@example.com".into());
assert_eq!(email.len(), 16); // delegates to String::len

Named Struct

use moxy::Deref;

#[derive(Deref)]
struct Email {
    raw: String,
}

let email = Email { raw: "john@example.com".into() };
assert_eq!(email.len(), 16);

Multi-Field Structs

When a struct has multiple fields, mark the deref target with #[moxy(deref)]:

use moxy::Deref;

#[derive(Deref)]
struct User {
    name: String,
    #[moxy(deref)]
    email: String,
    phone: String,
}

let user = User {
    name: "John".into(),
    email: "john@example.com".into(),
    phone: "".into(),
};

assert_eq!(user.len(), 16); // delegates to email.len()

Warning

Omitting #[moxy(deref)] on a multi-field struct is a compile error — the macro cannot infer which field to delegate to.

Without #[moxy(deref)] on a multi-field struct, the macro will produce a compile error asking you to specify which field to target.

Use Cases

Note

Deref is intended for the newtype pattern. Delegating to an unrelated field in a multi-field struct can cause surprising behavior for callers who use * or method resolution.

The Deref derive is ideal for the newtype pattern:

use moxy::Deref;

#[derive(Deref)]
struct Username(String);

#[derive(Deref)]
struct Port(u16);

let name = Username("alice".into());
let port = Port(8080);

// Access all String methods on Username
assert!(name.starts_with("ali"));
assert_eq!(name.to_uppercase(), "ALICE");

// Access all u16 methods on Port
assert_eq!(port.leading_zeros(), 3);

Build

The Build derive macro generates a type-safe fluent builder for your struct. Annotate fields with #[moxy(build)] to include them in the builder, then call YourStruct::new() to get a builder instance.

The builder uses const generic bools to track which required fields have been set. Missing a required field is a compile error — not a runtime panic. Setters can be called in any order, and setting a required field twice is also a compile error.

Basic Usage

use moxy::Build;

#[derive(Build, Default)]
struct Config {
    #[moxy(build)]
    pub host: String,
    #[moxy(build)]
    pub port: u16,
}

let config = Config::new()
    .host("localhost")
    .port(8080_u16)
    .build();

assert_eq!(config.host, "localhost");
assert_eq!(config.port, 8080);

Build generates:

  • A ConfigBuilder struct with const generic bools tracking required field state
  • A setter method per annotated field that accepts V: Into<T> for flexible conversions
  • A build() method — only available once all required fields are set
  • A Config::new() factory that returns a fresh ConfigBuilder

Non-annotated fields are initialised with Default::default() via a struct spread.

Note

Build requires the struct to also implement Default — either #[derive(Default)] or a manual impl Default. This is needed to initialise unannotated fields via the struct spread in build().

What’s Next

  • SettersV: Into<T> setter pattern, partial annotation, required vs optional fields
  • Defaults — inline fallback values with default = <expr>
  • Custom Names — override the generated setter method name
  • Generics — using Build with generic structs

Setters

Every field annotated with #[moxy(build)] gets a fluent setter method on the generated builder. Setters accept any value that implements Into<T>, so callers are not forced to construct the exact field type.

Note

All missing-field errors are compile errors, not runtime panics. The typestate guarantees that build() cannot be called until every required field has been set.

V: Into<T> Signature

use moxy::Build;

#[derive(Build, Default)]
struct Server {
    #[moxy(build)]
    pub host: String,
    #[moxy(build)]
    pub port: u16,
}

// &str is accepted because &str: Into<String>
let s = Server::new().host("localhost").port(8080_u16).build();
assert_eq!(s.host, "localhost");

Required setters consume the builder and return a new type, advancing the typestate. Optional setters (fields with a default) mutate in place and return Self.

Partial Annotation

Only annotate the fields you want in the builder. Fields without #[moxy(build)] are not exposed as setters — they receive Default::default() when build() is called:

use moxy::Build;

#[derive(Build, Default)]
struct Connection {
    #[moxy(build)]
    pub host: String,
    #[moxy(build)]
    pub port: u16,
    pub timeout: u64,   // not in builder — gets 0u64
}

let conn = Connection::new().host("127.0.0.1").port(5432_u16).build();
assert_eq!(conn.timeout, 0u64);

Required vs Optional Fields

A field annotated with bare #[moxy(build)] is requiredbuild() is not available until all required fields are set. Forgetting one is a compile error:

use moxy::Build;

#[derive(Build, Default)]
struct Config {
    #[moxy(build)]
    pub host: String,
    #[moxy(build)]
    pub port: u16,
}

// error[E0599]: no method named `build` found for struct `ConfigBuilder<true>`
//  --> src/main.rs:10:35
//   |
//   | Config::new().host("localhost").build();
//   |                                 ^^^^^ method not found in `ConfigBuilder<true>`
//   |
//   = note: the method was found for
//           - `ConfigBuilder<true, true>`
Config::new().host("localhost").build();

Caution

Setting a required field twice is also a compile error — the setter is consumed after first use and the updated builder type no longer has that method.

Setting a required field twice is also a compile error — the setter is consumed after use:

use moxy::Build;

#[derive(Build, Default)]
struct Config {
    #[moxy(build)]
    pub host: String,
    #[moxy(build)]
    pub port: u16,
}

// error[E0599]: no method named `host` found for struct `ConfigBuilder<true>`
//   |
//   | Config::new().host("a").host("b").port(80_u16).build();
//   |                         ^^^^ method not found in `ConfigBuilder<true>`
//   |
//   = note: method `host` is available on `ConfigBuilder`
Config::new().host("a").host("b").port(80_u16).build();

To make a field optional, provide a fallback with default = <expr> or use an Option<T> type.

Option<T> Fields

Fields with an Option<T> type are automatically optional — no default attribute needed. The setter accepts the inner type T and wraps it in Some:

use moxy::Build;

#[derive(Build, Default)]
struct Profile {
    #[moxy(build)]
    pub name: String,
    #[moxy(build)]
    pub bio: Option<String>,
}

// bio is optional — defaults to None
let p = Profile::new().name("alice").build();
assert_eq!(p.bio, None);

// setter accepts &str (not Option<&str>) and wraps in Some
let p = Profile::new().name("alice").bio("hello").build();
assert_eq!(p.bio, Some("hello".to_string()));

Any Order

Required setters can be called in any order:

use moxy::Build;

#[derive(Build, Default)]
struct Config {
    #[moxy(build)]
    pub host: String,
    #[moxy(build)]
    pub port: u16,
}

let a = Config::new().host("localhost").port(8080_u16).build();
let b = Config::new().port(8080_u16).host("localhost").build();

assert_eq!(a.host, b.host);
assert_eq!(a.port, b.port);

Defaults

Use default = <expr> inside build(...) to make a field optional in the builder. When build() is called without setting the field, the expression is evaluated and its result is used as the value.

Basic Default

use moxy::Build;

#[derive(Build, Default)]
struct Server {
    #[moxy(build(default = "localhost"))]
    pub host: String,
    #[moxy(build(default = 8080u16))]
    pub port: u16,
    #[moxy(build)]
    pub name: String,
}

// Only `name` is required; host and port fall back to their defaults.
let s = Server::new().name("api").build();
assert_eq!(s.host, "localhost");
assert_eq!(s.port, 8080u16);

// Defaults can still be overridden.
let s = Server::new().host("0.0.0.0").port(443u16).name("tls").build();
assert_eq!(s.host, "0.0.0.0");

impl Into<T> Expressions

The default expression is passed through the same Into<T> conversion used by the setter, so it does not need to be the exact field type — it only needs to implement Into<T>:

use moxy::Build;

#[derive(Build, Default)]
struct App {
    // &str → String via Into
    #[moxy(build(default = "localhost"))]
    pub host: String,

    // method call returning String — identity Into
    #[moxy(build(default = "api".to_string()))]
    pub name: String,

    // Vec::new() — Vec<String>: Into<Vec<String>>
    #[moxy(build(default = Vec::new()))]
    pub tags: Vec<String>,
}

Constants and Expressions

Any valid Rust expression works — including named constants, function calls, and more complex expressions:

use moxy::Build;

const DEFAULT_RETRIES: u32 = 3;

#[derive(Build, Default)]
struct Client {
    #[moxy(build(default = DEFAULT_RETRIES))]
    pub retries: u32,
}

let c = Client::new().build();
assert_eq!(c.retries, 3);

The expression is placed inside unwrap_or_else(|| <expr>.into()), so it is evaluated lazily — only when the field was not set.

Tip

Because defaults are evaluated lazily, expensive expressions like Vec::new() or function calls are only executed if the field was not set by the caller — there is no cost when the field is provided.

Custom Names

By default the builder setter is named after the struct field. Pass a string literal as the first argument to build(...) to override it.

Renaming a Setter

use moxy::Build;

#[derive(Build, Default)]
struct Credentials {
    #[moxy(build("username"))]
    pub user: String,
    #[moxy(build)]
    pub password: String,
}

let c = Credentials::new()
    .username("alice")   // setter is `username`, field is `user`
    .password("secret")
    .build();

assert_eq!(c.user, "alice");

The field name in the struct (user) and the generated setter name (username) are independent — the struct field is always assigned correctly.

Combining with a Default

A custom name and a default value can be used together:

use moxy::Build;

#[derive(Build, Default)]
struct Service {
    #[moxy(build("addr", default = "0.0.0.0"))]
    pub address: String,
}

// Field is optional — falls back to "0.0.0.0"
let s = Service::new().build();
assert_eq!(s.address, "0.0.0.0");

// Override via the custom setter name
let s = Service::new().addr("127.0.0.1").build();
assert_eq!(s.address, "127.0.0.1");

Generics

Build works with generic structs. Type parameters and where-clause bounds are forwarded to the generated builder type unchanged.

Basic Generic Struct

use moxy::Build;

#[derive(Build, Default)]
struct Wrapper<T: Default> {
    #[moxy(build)]
    pub value: T,
}

let w: Wrapper<u32> = Wrapper::new().value(42u32).build();
assert_eq!(w.value, 42u32);

The generated builder is WrapperBuilder<T, const VALUE: bool> — type parameters are forwarded and const bool parameters track required fields. Wrapper::new() returns a WrapperBuilder<T> with all const generics defaulted to false. All generics are inferred from the call site.

With Defaults

Generic fields can have defaults too, as long as the default expression is compatible with the type parameter’s bounds:

use moxy::Build;

#[derive(Build, Default)]
struct Container<T: Default + Clone> {
    #[moxy(build)]
    pub value: T,
    pub label: String,   // not in builder — receives Default::default()
}

let c: Container<i32> = Container::new().value(99).build();
assert_eq!(c.value, 99);
assert_eq!(c.label, "");

Default

The Default derive macro generates an impl Default for your struct, with per-field custom default values. Annotate fields with #[moxy(default = expr)] to override the standard Default::default().

When imported via use moxy::Default, this derive shadows std’s built-in Default derive. Fields without #[moxy(default = ...)] still receive their normal Default::default() value.

Note

use moxy::Default shadows Rust’s built-in Default derive. If you need both in the same module, qualify the std one explicitly: #[derive(std::default::Default)].

Basic Usage

use moxy::Default;

#[derive(Default)]
struct Config {
    #[moxy(default = "localhost")]
    pub host: String,
    #[moxy(default = 8080u16)]
    pub port: u16,
    pub debug: bool,
}

let config = Config::default();
assert_eq!(config.host, "localhost");
assert_eq!(config.port, 8080);
assert_eq!(config.debug, false); // standard Default::default()

What Gets Generated

The macro generates a standard impl Default for Config block. Each annotated field uses its expression (passed through .into()), while unannotated fields fall back to Default::default():

impl Default for Config {
    fn default() -> Self {
        Self {
            host: "localhost".into(),
            port: 8080u16.into(),
            debug: Default::default(),
        }
    }
}

When to Use

Use moxy’s Default instead of std’s when you need per-field default values without writing the impl block by hand. For structs where every field is fine with its type’s Default::default(), std’s built-in derive is sufficient.

What’s Next

  • Expressions — literals, constants, method calls, and Into<T> coercion
  • Struct Types — named structs, tuple structs, unit structs, and generics

Expressions

The default = <expr> attribute accepts any valid Rust expression — string literals, typed literals, constants, and method calls. The expression is passed through .into(), so it does not need to match the exact field type.

Literals

String literals and typed numeric literals work directly:

use moxy::Default;

#[derive(Default)]
struct Server {
    #[moxy(default = "0.0.0.0")]
    pub bind: String,
    #[moxy(default = 443u16)]
    pub port: u16,
    #[moxy(default = true)]
    pub tls: bool,
}

let s = Server::default();
assert_eq!(s.bind, "0.0.0.0");
assert_eq!(s.port, 443);
assert_eq!(s.tls, true);

impl Into<T> Coercion

The default expression is wrapped in .into(), so any type implementing Into<T> for the field type works:

use moxy::Default;

#[derive(Default)]
struct App {
    // &str → String via Into
    #[moxy(default = "my-app")]
    pub name: String,

    // method call returning String — identity Into
    #[moxy(default = "api".to_string())]
    pub prefix: String,
}

let app = App::default();
assert_eq!(app.name, "my-app");
assert_eq!(app.prefix, "api");

Constants

Named constants and static values work as expressions:

use moxy::Default;

const MAX_RETRIES: u32 = 3;

#[derive(Default)]
struct Client {
    #[moxy(default = MAX_RETRIES)]
    pub retries: u32,
}

let c = Client::default();
assert_eq!(c.retries, 3);

Complex Expressions

Any expression valid in value position works — function calls, constructor calls, and more:

use moxy::Default;

#[derive(Default)]
struct Collection {
    #[moxy(default = Vec::new())]
    pub items: Vec<String>,

    #[moxy(default = String::from("unnamed"))]
    pub label: String,
}

let c = Collection::default();
assert!(c.items.is_empty());
assert_eq!(c.label, "unnamed");

The expression is evaluated each time default() is called — there is no caching or static initialization.

Struct Types

The Default derive works with named structs, tuple structs, and unit structs.

Named Structs

The most common case — fields are referenced by name:

use moxy::Default;

#[derive(Default)]
struct Config {
    #[moxy(default = "localhost")]
    pub host: String,
    #[moxy(default = 8080u16)]
    pub port: u16,
    pub verbose: bool,
}

let c = Config::default();
assert_eq!(c.host, "localhost");
assert_eq!(c.port, 8080);
assert_eq!(c.verbose, false);

Tuple Structs

Positional fields work the same way:

use moxy::Default;

#[derive(Default)]
struct Endpoint(
    #[moxy(default = "0.0.0.0")] String,
    #[moxy(default = 3000u16)] u16,
);

let ep = Endpoint::default();
assert_eq!(ep.0, "0.0.0.0");
assert_eq!(ep.1, 3000);

Unit Structs

Unit structs have no fields, so #[derive(Default)] just generates Self:

use moxy::Default;

#[derive(Default)]
struct Marker;

let _ = Marker::default();

Generics

Type parameters are propagated through the generated impl block. Unannotated fields with generic types require T: Default:

use moxy::Default;

#[derive(Default)]
struct Container<T: Default> {
    #[moxy(default = 10u32)]
    pub capacity: u32,
    pub value: T,
}

let c: Container<String> = Container::default();
assert_eq!(c.capacity, 10);
assert_eq!(c.value, String::default());

With Build

When both Default and Build are derived, the builder’s ..Default::default() spread picks up moxy’s generated defaults automatically:

use moxy::{Build, Default};

#[derive(Build, Default)]
struct Server {
    #[moxy(build)]
    pub name: String,
    #[moxy(default = "0.0.0.0")]
    pub bind: String,
    #[moxy(default = 8080u16)]
    pub port: u16,
}

// Builder only requires `name` — bind and port come from Default
let s = Server::new().name("api").build();
assert_eq!(s.name, "api");
assert_eq!(s.bind, "0.0.0.0");
assert_eq!(s.port, 8080);

Enums

The Default derive works with enums. Mark one variant with #[moxy(default)] to use it as the default.

Unit Variant

use moxy::Default;

#[derive(Default, PartialEq, Debug)]
enum Status {
    Active,
    #[moxy(default)]
    Idle,
}

assert_eq!(Status::default(), Status::Idle);

Named Variant

Fields within the default variant use the same #[moxy(default = expr)] syntax as struct fields:

use moxy::Default;

#[derive(Default)]
enum Color {
    #[moxy(default)]
    Rgb {
        #[moxy(default = 255u8)]
        r: u8,
        g: u8,
        b: u8,
    },
    Hex(String),
}

let c = Color::default();
// c == Color::Rgb { r: 255, g: 0, b: 0 }

Tuple Variant

use moxy::Default;

#[derive(Default, PartialEq, Debug)]
enum Wrapper {
    #[moxy(default)]
    Value(#[moxy(default = 42)] i32),
    Empty,
}

assert_eq!(Wrapper::default(), Wrapper::Value(42));

Rules

  • Exactly one variant must be marked with #[moxy(default)]
  • Zero or multiple marked variants produce a compile error
  • Fields without #[moxy(default = expr)] use Default::default()

Get

The Get derive macro generates getter methods for struct fields. Annotate fields with #[moxy(get)] to opt in — unannotated fields get no getter.

Getters return through Deref::Target, so String fields return &str, Vec<T> returns &[T], etc. Use copy for primitives and clone for types like Arc<T>.

Note

Getters return &Deref::Target, not &T. This is more ergonomic for common types (String&str, Vec<T>&[T]), but for types without a natural Deref target, use get(copy) or get(clone) to get an owned value instead.

Basic Usage

use moxy::Get;

#[derive(Get)]
struct User {
    #[moxy(get)]
    name: String,
    #[moxy(get)]
    email: String,
    password_hash: String,
}

let user = User {
    name: "alice".into(),
    email: "a@b.com".into(),
    password_hash: "hash".into(),
};

assert_eq!(user.name(), "alice");    // fn name(&self) -> &str
assert_eq!(user.email(), "a@b.com"); // fn email(&self) -> &str
// user.password_hash() — no annotation, no getter

What’s Next

Modifiers

Copy

For Copy types and primitives, use get(copy) to return by value instead of by reference:

use moxy::Get;

#[derive(Get)]
struct Metrics {
    #[moxy(get(copy))]
    count: u32,
    #[moxy(get)]
    is_active: bool,
}

let m = Metrics { count: 42, is_active: true };
let c: u32 = m.count();    // fn count(&self) -> u32
assert!(m.is_active());    // fn is_active(&self) -> bool (bool auto-copies)

Bool fields automatically return by value — no copy modifier needed.

Note

bool fields always return by value regardless of modifier — you do not need get(copy) for booleans.

Clone

For types where you want an owned copy via .clone():

use moxy::Get;
use std::sync::Arc;

#[derive(Get)]
struct Labels {
    #[moxy(get(clone))]
    label: Arc<String>,
}

let l = Labels { label: Arc::new("test".into()) };
let v: Arc<String> = l.label();  // fn label(&self) -> Arc<String>

Mutable

Use get(mutable) to generate both a regular getter and a _mut variant:

use moxy::Get;

#[derive(Get)]
struct Buffer {
    #[moxy(get(mutable))]
    data: Vec<u8>,
}

let mut b = Buffer { data: vec![1, 2, 3] };
assert_eq!(b.data(), &[1, 2, 3]);  // fn data(&self) -> &[u8]
b.data_mut().push(4);              // fn data_mut(&mut self) -> &mut Vec<u8>
assert_eq!(b.data(), &[1, 2, 3, 4]);

Custom Name

Override the method name with a string literal:

use moxy::Get;

#[derive(Get)]
struct Row {
    #[moxy(get(copy, "id"))]
    row_id: u64,
}

let r = Row { row_id: 99 };
assert_eq!(r.id(), 99);  // fn id(&self) -> u64

Modifiers can be combined: #[moxy(get(copy, "id"))].

Option Fields

Option<T> fields return Option<&Deref::Target> via .as_deref(). For Option<String>, this means Option<&str> — more ergonomic than &Option<String> or Option<&String>.

use moxy::Get;

#[derive(Get)]
struct Profile {
    #[moxy(get)]
    bio: Option<String>,
}

let p = Profile { bio: Some("hello".into()) };
let bio: Option<&str> = p.bio();
assert_eq!(bio, Some("hello"));

let p = Profile { bio: None };
assert_eq!(p.bio(), None);

Callbacks

Use on = expr to run an expression before the getter returns. The expression has access to &self.

use moxy::Get;

static mut ACCESS_COUNT: u32 = 0;

#[derive(Get)]
struct Tracked {
    #[moxy(get(on = unsafe { ACCESS_COUNT += 1 }))]
    value: String,
}

let t = Tracked { value: "hello".into() };

unsafe { ACCESS_COUNT = 0 };
assert_eq!(t.value(), "hello");
assert_eq!(unsafe { ACCESS_COUNT }, 1);

Doc Forwarding

/// comments on fields are forwarded to the generated getter method:

use moxy::Get;

#[derive(Get)]
struct User {
    /// The user's display name
    #[moxy(get)]
    name: String,
}

// Generated method has the doc comment:
// /// The user's display name
// pub fn name(&self) -> &str { &self.name }

Set

The Set derive macro generates setter methods for struct fields. Annotate fields with #[moxy(set)] to opt in — unannotated fields get no setter.

Setters use Into<T> for flexible type coercion (consistent with Build) and return &mut Self for chaining.

Note

Set setters return &mut Self and borrow mutably — they do not consume self. This is different from Build setters, which consume the builder to advance the typestate.

Basic Usage

use moxy::Set;

#[derive(Set)]
struct Config {
    #[moxy(set)]
    host: String,
    #[moxy(set)]
    port: u16,
    read_only: bool,
}

let mut cfg = Config { host: String::new(), port: 0, read_only: true };

// &str → String via Into, chaining via &mut Self
cfg.set_host("localhost").set_port(8080_u16);

assert_eq!(cfg.host, "localhost");
assert_eq!(cfg.port, 8080);
// cfg.set_read_only() — no annotation, no setter

What’s Next

  • Option Fields — automatic Some wrapping for Option<T> fields
  • Callbacks — transforms and side effects with on = expr

Option Fields

Option<T> fields are handled automatically — the setter accepts the inner type T and wraps it in Some. This is consistent with the Build macro’s Option<T> handling.

use moxy::Set;

#[derive(Set)]
struct Profile {
    #[moxy(set)]
    bio: Option<String>,
}

let mut p = Profile { bio: None };

// Accepts &str (Into<String>), wraps in Some
p.set_bio("hello");
assert_eq!(p.bio, Some("hello".to_string()));

The generated setter signature is fn set_bio<V: Into<String>>(&mut self, value: V) -> &mut Self.

Callbacks

Use on = expr to transform the value before assignment. The expression receives value: T (already converted via Into) and its return value is what gets assigned.

Transform

The callback expression replaces the assigned value:

use moxy::Set;

#[derive(Set)]
struct Config {
    #[moxy(set(on = value.to_lowercase()))]
    host: String,
}

let mut cfg = Config { host: String::new() };
cfg.set_host("LOCALHOST");
assert_eq!(cfg.host, "localhost");

Generated:

fn set_host<V: Into<String>>(&mut self, value: V) -> &mut Self {
    let value: String = value.into();
    self.host = value.to_lowercase();
    self
}

Side Effects

For side effects without transforming the value, return value from a block:

use moxy::Set;

#[derive(Set)]
struct Config {
    #[moxy(set(on = { println!("host changed to {}", value); value }))]
    host: String,
}

Custom Name

Override the setter name with a string literal:

use moxy::Set;

#[derive(Set)]
struct Row {
    #[moxy(set("update_id"))]
    row_id: u64,
}

let mut r = Row { row_id: 0 };
r.update_id(99_u64);
assert_eq!(r.row_id, 99);

Doc Forwarding

/// comments on fields are forwarded to the generated setter method, just like with Get.

Union

#[derive(Union)] generates variant accessor methods for enums: is_*(), as_*(), and to_*().

use moxy::Union;

#[derive(Union)]
enum Shape {
    Circle(f64),
    Rect { width: f64, height: f64 },
    Point,
}

This generates:

  • is_circle(), is_rect(), is_point() — boolean predicates
  • as_circle(), as_rect() — borrow variant data as Option<&T> or Option<(&T1, &T2)>
  • to_circle(), to_rect() — clone variant data as Option<T> or Option<(T1, T2)>

Unit variants only get is_*() since there is no data to access.

Attribute Helpers

Use #[moxy(variant(...))] on individual variants:

AttributeEffect
alias = "name"Rename generated methods (is_name, as_name, to_name)
skipSkip all method generation for this variant
use moxy::Union;

#[derive(Union)]
enum Token {
    #[moxy(variant(alias = "ident"))]
    Identifier(String),
    #[moxy(variant(skip))]
    Eof,
}

let t = Token::Identifier("foo".into());
assert!(t.is_ident());
assert_eq!(t.as_ident(), Some(&"foo".to_string()));

See Also

Variant Methods

Union generates three kinds of methods for each variant. Method names are derived from the variant name converted to snake_case.

is_* — Boolean Predicates

Generated for all variant kinds (unit, tuple, named):

use moxy::Union;

#[derive(Union)]
enum Status {
    Active,
    Paused(String),
    Error { code: i32, message: String },
}

let s = Status::Active;
assert!(s.is_active());
assert!(!s.is_paused());
assert!(!s.is_error());

as_* — Borrow Variant Data

Generated for tuple and named variants. Returns Option<&T> for single-field variants, Option<(&T1, &T2, ...)> for multi-field.

use moxy::Union;

#[derive(Union)]
enum Value {
    Text(String),
    Pair(i32, String),
    Record { id: u64, name: String },
}

let v = Value::Text("hello".into());
assert_eq!(v.as_text(), Some(&"hello".to_string()));

let v = Value::Pair(1, "one".into());
assert_eq!(v.as_pair(), Some((&1, &"one".to_string())));

let v = Value::Record { id: 42, name: "alice".into() };
assert_eq!(v.as_record(), Some((&42u64, &"alice".to_string())));

Returns None when the variant doesn’t match:

let v = Value::Text("hello".into());
assert_eq!(v.as_pair(), None);

to_* — Clone Variant Data

Same signatures as as_* but clones the data. Requires Clone on the inner types.

use moxy::Union;

#[derive(Union)]
enum Token {
    Ident(String),
    Literal(i32),
}

let t = Token::Ident("foo".into());
assert_eq!(t.to_ident(), Some("foo".to_string()));
assert_eq!(t.to_literal(), None);

Aliasing

Use #[moxy(variant(alias = "name"))] to control the generated method names:

use moxy::Union;

#[derive(Union)]
enum Expr {
    #[moxy(variant(alias = "num"))]
    NumberLiteral(f64),
}

let e = Expr::NumberLiteral(3.14);
assert!(e.is_num());
assert_eq!(e.as_num(), Some(&3.14));

Skipping Variants

Use #[moxy(variant(skip))] to exclude a variant from method generation:

use moxy::Union;

#[derive(Union)]
enum Event {
    Click(i32, i32),
    #[moxy(variant(skip))]
    Internal(Vec<u8>),
}

No is_internal, as_internal, or to_internal methods are generated.

Common Fields

When all named variants of an enum share a field with the same name and type, Union auto-generates a shared accessor that dispatches via match.

use moxy::Union;

#[derive(Union)]
enum Animal {
    Dog { name: String, breed: String },
    Cat { name: String, color: String },
    Bird { name: String, wingspan: f64 },
}

let dog = Animal::Dog { name: "Rex".into(), breed: "Lab".into() };
let cat = Animal::Cat { name: "Whiskers".into(), color: "orange".into() };

assert_eq!(dog.name(), "Rex");
assert_eq!(cat.name(), "Whiskers");

The generated method returns a reference:

// generated:
impl Animal {
    pub fn name(&self) -> &String {
        match self {
            Self::Dog { name, .. } => name,
            Self::Cat { name, .. } => name,
            Self::Bird { name, .. } => name,
        }
    }
}

Rules

  • Only named variants participate — tuple and unit variants are ignored
  • The field must have the same name and the same type in every named variant
  • Variants with #[moxy(variant(skip))] are excluded from detection
  • Common field accessors are generated alongside the per-variant is_*/as_*/to_* methods
  • At least two named variants are required for common field detection

unionize!

The unionize! macro generates a union-type enum with single-field tuple variants, variant accessor methods, and From impls — all in one invocation.

Syntax

moxy::unionize! {{
    VariantName => Type,
    ...
} as [visibility] EnumName}

Example

moxy::unionize! {{
    Text => String,
    Number => i32,
    Flag => bool,
} as pub Value}

This generates:

pub enum Value {
    Text(String),
    Number(i32),
    Flag(bool),
}

impl Value {
    pub fn is_text(&self) -> bool { ... }
    pub fn as_text(&self) -> Option<&String> { ... }
    pub fn to_text(&self) -> Option<String> { ... }

    pub fn is_number(&self) -> bool { ... }
    pub fn as_number(&self) -> Option<&i32> { ... }
    pub fn to_number(&self) -> Option<i32> { ... }

    pub fn is_flag(&self) -> bool { ... }
    pub fn as_flag(&self) -> Option<&bool> { ... }
    pub fn to_flag(&self) -> Option<bool> { ... }
}

impl From<String> for Value { ... }
impl From<i32> for Value { ... }
impl From<bool> for Value { ... }

From Impls

Each variant type gets a From implementation, enabling direct conversion:

moxy::unionize! {{
    Text => String,
    Num => i32,
} as Value}

let v: Value = "hello".to_string().into();
assert!(v.is_text());

let v: Value = 42.into();
assert!(v.is_num());

Visibility

The enum visibility is controlled by the keyword before the name:

moxy::unionize! {{ A => i32 } as pub MyType}       // pub
moxy::unionize! {{ A => i32 } as pub(crate) MyType} // pub(crate)
moxy::unionize! {{ A => i32 } as MyType}            // private

Forward

#[derive(Forward)] generates methods that delegate to inner fields (structs) or variant payloads (enums). You declare the method signatures to forward using #[moxy(forward(fn ...))].

Since proc macros cannot see type information, you explicitly declare which methods to forward.

use moxy::Forward;

#[derive(Forward)]
struct Cache {
    #[moxy(forward(
        fn len(&self) -> usize,
        fn is_empty(&self) -> bool,
    ))]
    store: Vec<String>,
}

let cache = Cache { store: vec!["a".into(), "b".into()] };
assert_eq!(cache.len(), 2);
assert!(!cache.is_empty());

See Also

  • Structs — forwarding to struct fields
  • Enums — forwarding across enum variants

Structs

Place #[moxy(forward(fn ...))] on a field to generate methods on the parent struct that delegate to that field.

Basic Usage

use moxy::Forward;

#[derive(Forward)]
struct App {
    #[moxy(forward(
        fn len(&self) -> usize,
        fn is_empty(&self) -> bool,
        fn push(&mut self, item: String),
    ))]
    items: Vec<String>,
}

let mut app = App { items: vec![] };
app.push("hello".into());
assert_eq!(app.len(), 1);

Both &self and &mut self methods are supported. Arguments are passed through directly.

Multiple Fields

Different fields can forward different methods:

use moxy::Forward;

struct Config { name: String }
impl Config {
    fn name(&self) -> &str { &self.name }
}

struct Transport { log: Vec<String> }
impl Transport {
    fn send(&mut self, msg: &str) { self.log.push(msg.into()); }
}

#[derive(Forward)]
struct Service {
    #[moxy(forward(fn name(&self) -> &str))]
    config: Config,
    #[moxy(forward(fn send(&mut self, msg: &str)))]
    transport: Transport,
}

let mut svc = Service {
    config: Config { name: "api".into() },
    transport: Transport { log: vec![] },
};
assert_eq!(svc.name(), "api");
svc.send("hello");

Enums

For enums, place #[moxy(forward(fn ...))] on the enum itself. A match dispatch is generated across all variants.

Tuple Variants

use moxy::Forward;

struct Circle { radius: f64 }
impl Circle { fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius } }

struct Rect { width: f64, height: f64 }
impl Rect { fn area(&self) -> f64 { self.width * self.height } }

#[derive(Forward)]
#[moxy(forward(fn area(&self) -> f64))]
enum Shape {
    Circle(Circle),
    Rect(Rect),
}

let c = Shape::Circle(Circle { radius: 1.0 });
assert!((c.area() - std::f64::consts::PI).abs() < 1e-10);

For single-field tuple variants, the payload is bound and the method is called on it.

Named Variants

For named variants, the first field is used by default. Use #[moxy(forward)] on a specific field to override:

use moxy::Forward;

struct Runner { name: String }
impl Runner { fn run(&self) -> &str { &self.name } }

#[derive(Forward)]
#[moxy(forward(fn run(&self) -> &str))]
enum Task {
    Quick(Runner),
    Full {
        #[moxy(forward)]
        runner: Runner,
        priority: u8,
    },
}

Skipping Variants

Use #[moxy(forward(skip))] to exclude a variant from forwarding. The generated match arm calls unreachable!().

use moxy::Forward;

struct Worker;
impl Worker { fn exec(&self) -> i32 { 42 } }

#[derive(Forward)]
#[moxy(forward(fn exec(&self) -> i32))]
enum Action {
    Do(Worker),
    #[moxy(forward(skip))]
    Noop,
}

Unit variants without skip produce a compile error since there is no payload to forward to.

Feature Flags

Moxy uses Cargo feature flags to keep optional functionality behind compile-time gates.

derive (default)

Enables all derive macros (Display, Deref, Build, Default, Get, Set, Union, Forward) and the unionize! macro. This feature is enabled by default.

[dependencies]
moxy = "0.0.4"

json

Note

serde must be added as a separate dependency — moxy does not re-export it.

Enables the json display format, which serializes structs to JSON via serde_json. Your crate must also depend on serde:

[dependencies]
moxy = { version = "0.0.4", features = ["json"] }
serde = { version = "1", features = ["derive"] }

See JSON for usage.

color

Enables ANSI truecolor output via the colored crate.

[dependencies]
moxy = { version = "0.0.4", features = ["color"] }

See Color for usage.

full

Enables both json and color:

[dependencies]
moxy = { version = "0.0.4", features = ["full"] }
serde = { version = "1", features = ["derive"] }

Attribute Reference

All moxy attributes use the #[moxy(...)] syntax. This page is a quick reference for every available attribute.

Display — Struct Level

AttributeDescriptionExample
display(debug)Quote string values#[moxy(display(debug))]
display(compact)Values only, space-separated#[moxy(display(compact))]
display(keyvalue)key=value pairs#[moxy(display(keyvalue))]
display(map)Map style without type name#[moxy(display(map))]
display(json)JSON serialization (requires json feature)#[moxy(display(json))]
display(pretty)Multi-line output (modifier)#[moxy(display(pretty))]
display(color)Colored output with default theme (requires color feature)#[moxy(display(color))]
display(color = "theme")Colored output with named theme#[moxy(display(color = "dracula"))]
display(alias = "name")Rename the type in output#[moxy(display(alias = "Person"))]
display("fmt", exprs...)Custom format string#[moxy(display("{}", self.name))]

Modifiers can be combined in a single attribute: #[moxy(display(debug, pretty, color))]

They can also be split across multiple #[moxy(...)] attributes — arguments are merged automatically:

#[moxy(display(debug))]
#[moxy(display(pretty))]    // equivalent to #[moxy(display(debug, pretty))]

Specifying the same option twice with different values is a compile error:

#[moxy(display(alias = "A"))]
#[moxy(display(alias = "B"))]  // error: conflicting values for `alias`

#[moxy(display(compact))]
#[moxy(display(debug))]        // error: conflicting display styles

Display — Field Level

AttributeDescriptionExample
display(skip)Exclude field from output#[moxy(display(skip))]
display(alias = "name")Rename field in output#[moxy(display(alias = "full_name"))]

Build — Field Level

AttributeDescriptionExample
buildInclude field in builder (compile error if unset at build time)#[moxy(build)]
build("name")Include field with a custom setter method name#[moxy(build("username"))]
build(default = expr)Include field with a fallback value (optional in builder)#[moxy(build(default = 8080u16))]
build("name", default = expr)Custom setter name + default value#[moxy(build("port", default = 8080u16))]

Default — Field Level

AttributeDescriptionExample
default = exprUse expression as field’s default value (passed through .into())#[moxy(default = "localhost")]

Supports literals, typed literals, constants, and arbitrary expressions:

#[moxy(default = "hello")]          // string literal
#[moxy(default = 8080u16)]          // typed literal
#[moxy(default = MAX_RETRIES)]      // constant
#[moxy(default = Vec::new())]       // expression

Get — Field Level

AttributeDescriptionExample
getGenerate getter (returns &Deref::Target — e.g. String&str. Use copy for primitives)#[moxy(get)]
get("name")Custom getter method name#[moxy(get("id"))]
get(copy)Return by value (for Copy types and primitives like u32, bool)#[moxy(get(copy))]
get(clone)Return by clone#[moxy(get(clone))]
get(mutable)Also generate field_mut(&mut self) -> &mut T#[moxy(get(mutable))]
get(on = expr)Run expression before returning#[moxy(get(on = log::debug!("read")))]

Set — Field Level

AttributeDescriptionExample
setGenerate setter (fn set_field(&mut self, value: impl Into<T>) -> &mut Self)#[moxy(set)]
set("name")Custom setter method name (replaces set_field)#[moxy(set("update_id"))]
set(on = expr)Transform: expression result is assigned (value: T in scope)#[moxy(set(on = value.to_lowercase()))]

Deref — Field Level

AttributeDescriptionExample
derefMark field as deref target (required for multi-field structs)#[moxy(deref)]

Union — Variant Level

AttributeDescriptionExample
variant(alias = "name")Rename generated methods (is_name, as_name, to_name)#[moxy(variant(alias = "ident"))]
variant(skip)Skip all method generation for this variant#[moxy(variant(skip))]

unionize! — Macro Syntax

moxy::unionize! {{
    VariantName => Type,
    ...
} as [visibility] EnumName}

Generates an enum with tuple variants, is_*/as_*/to_* methods, and From<Type> impls.

Forward — Struct Field Level

AttributeDescriptionExample
forward(fn sig, ...)Declare methods to forward to this field#[moxy(forward(fn len(&self) -> usize))]

Forward — Enum Level

AttributeDescriptionExample
forward(fn sig, ...)Declare methods forwarded across all variants#[moxy(forward(fn area(&self) -> f64))]

Forward — Enum Variant Level

AttributeDescriptionExample
forward(skip)Exclude variant from forwarding (generates unreachable!())#[moxy(forward(skip))]
forwardMark which field to forward to in named variants (defaults to first)#[moxy(forward)]

Color Themes

Theme NameStruct NameFieldsValuesPunctuation
dracula (default)cyanpinkyellowwhite
atom-one-darkgoldpurplegreengray
github-darkblueredlight bluelight gray