Skip to main content

Crate derive_defs

Crate derive_defs 

Source
Expand description

§derive-defs

Library for generating derive preset macros from TOML configuration.

This crate provides functionality to parse TOML configuration files and generate proc-macro code for derive attribute presets.

§How It Works

derive-defs generates proc-macro code during the build process:

  1. You define macro presets in derive_defs.toml
  2. During cargo build, the code generator creates Rust proc-macro implementations
  3. The generated code is written to $OUT_DIR/derive_defs.rs
  4. You include this code in your proc-macro crate using include!
  5. You use the generated macros in your library or binary code

Important: Proc-macros can only be defined in a separate proc-macro type crate. Binary crates cannot define and use proc-macros in the same crate.

§Project Setup Guide

§Scenario 1: Library Crate with Inline Macros

Use when: Your library generates its own macros and exports them.

Structure:

my-library/
├── Cargo.toml              # [lib] proc-macro = true
├── build.rs                # Generates macros
├── derive_defs.toml        # Macro definitions
└── src/
    └── lib.rs              # Includes generated macros

Cargo.toml:

[package]
name = "my-library"
version = "0.1.0"
edition = "2024"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1"
quote = "1"
syn = { version = "2", features = ["full"] }

[build-dependencies]
derive-defs = "0.1"

build.rs:

fn main() {
    derive_defs::generate("derive_defs.toml")
        .expect("Failed to generate derive defs");
}

src/lib.rs:

// Include the generated proc-macro code
include!(concat!(env!("OUT_DIR"), "/derive_defs.rs"));

Use when: You have a binary application that wants to use derive macros.

Important: Binary crates cannot define proc-macros that they also use. You must create a separate proc-macro crate in the workspace.

Structure:

my-app/
├── Cargo.toml              # [workspace] with members
├── macros/                 # Proc-macro crate
│   ├── Cargo.toml          # [lib] proc-macro = true
│   ├── build.rs            # Generates macros
│   ├── derive_defs.toml    # Macro definitions
│   └── src/
│       └── lib.rs          # Includes generated macros
└── src/
    └── main.rs             # Uses macros from `macros` crate

Root Cargo.toml:

[workspace]
members = ["macros", "src"]
resolver = "2"

macros/Cargo.toml:

[package]
name = "my-app-macros"
version = "0.1.0"
edition = "2024"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1"
quote = "1"
syn = { version = "2", features = ["full"] }

[build-dependencies]
derive-defs = "0.1"

macros/build.rs:

fn main() {
    derive_defs::generate("derive_defs.toml")
        .expect("Failed to generate derive defs");
}

macros/src/lib.rs:

include!(concat!(env!("OUT_DIR"), "/derive_defs.rs"));

macros/derive_defs.toml:

[defs.config]
traits = ["Debug", "Clone", "Default"]

[defs.model]
traits = ["Debug", "Clone", "PartialEq"]

src/Cargo.toml:

[package]
name = "my-app"
version = "0.1.0"
edition = "2024"

[[bin]]
name = "my-app"
path = "main.rs"

[dependencies]
my-app-macros = { path = "../macros" }

src/main.rs:

use my_app_macros::{Config, Model};

#[config]
struct AppConfig {
    host: String,
    port: u16,
}

#[model]
struct User {
    name: String,
    age: u32,
}

fn main() {
    let config = AppConfig {
        host: "localhost".to_string(),
        port: 8080,
    };
    println!("{:?}", config);
}

§Why Separate Proc-Macro Crate?

Rust’s proc-macro system has a fundamental limitation: proc-macros must be defined in a separate crate from where they are used. This is because:

  1. Proc-macros are compiled before the rest of the crate
  2. A binary crate cannot simultaneously define and consume macros
  3. The macro expansion happens during compilation, not at runtime

Error you’ll see if you try to use macros in the same binary crate:

error: can't use a procedural macro from the same crate that defines it

Solution: Create a separate macros package in your workspace.

§Using include! vs include_str!

This crate uses include! macro for the generated code:

include!(concat!(env!("OUT_DIR"), "/derive_defs.rs"));
  • include!: Includes the file as Rust code (what we use)
  • include_str!: Includes the file as a &'static str string

The generated code must be included as Rust code because it contains actual function definitions that the compiler needs to parse.

§Quick Start

  1. Create a derive_defs.toml configuration file:
[defs.serialization]
traits = ["Clone", "Serialize", "Deserialize"]
attrs = ['#[serde(rename_all = "camelCase")]']

[defs.model]
traits = ["Debug", "Clone", "PartialEq"]
  1. Add to your build.rs:
derive_defs::generate("derive_defs.toml")
    .expect("Failed to generate derive defs");
  1. Include generated code in your lib.rs (in the proc-macro crate):
include!(concat!(env!("OUT_DIR"), "/derive_defs.rs"));
  1. Use the generated macros in your code:
use my_crate_macros::*;

#[serialization]
struct User {
    name: String,
    age: u32,
}

§Features

  • Declarative Configuration: Define derive presets in TOML, not code
  • Inheritance: Bundles can extend other bundles via extends
  • Cross-file Imports: Split configuration across multiple files
  • Runtime Modification: Use omit/add to modify bundles at use site

§TOML Configuration

§Basic Syntax

[defs.<name>]
traits = ["Trait1", "Trait2"]  # List of derive traits
attrs = ["#[attr]"]             # Additional attributes
extends = "<parent>"            # Inheritance (optional)

§Inheritance

[defs.base]
traits = ["Debug", "Clone"]

[defs.value_object]
extends = "base"
traits = ["PartialEq", "Eq", "Hash"]
# Result: Debug, Clone, PartialEq, Eq, Hash

§Cross-file Imports

[includes]
common = "shared/common_defs.toml"

[defs.api_response]
extends = "common.serialization"
traits = ["Default"]

§Namespaced Definitions

[defs.cli.config]
traits = ["Debug", "Default"]

[defs.cli.args]
traits = ["Debug", "Clone"]

[defs.web.model]
traits = ["Debug", "Clone", "Serialize"]

Generates macros: cli_config, cli_args, web_model.

§API Usage

§Attribute Modifiers

// Basic usage
#[serialization]
struct User { ... }

// Exclude Clone from the bundle
#[serialization(omit(Clone))]
struct Session { ... }

// Add Default and Hash to the bundle
#[serialization(add(Default, Hash))]
struct Config { ... }

// Exclude serde attributes
#[serialization(omit_attrs(serde))]
struct Internal { ... }

// Combination
#[serialization(omit(Clone), add(Copy))]
struct Flags { ... }

§Common Patterns

§Configuration Structs

[defs.config]
traits = ["Debug", "Clone", "Default"]
#[config]
struct ServerConfig {
    host: String,
    port: u16,
}

§Domain Models

[defs.model]
traits = ["Debug", "Clone", "PartialEq", "Eq"]
#[model]
struct User {
    id: u32,
    name: String,
}

§Serialization-Ready Types

[defs.serializable]
traits = ["Serialize", "Deserialize"]
attrs = ['#[serde(rename_all = "camelCase")]']
#[serializable]
struct ApiResponse {
    status_code: u16,
    message: String,
}

§Error Handling

The crate provides clear error messages at build.rs time:

  • Circular inheritance detection
  • Undefined parent references
  • Missing include files
  • Duplicate definition names

§Common Pitfalls

Error: can't use a procedural macro from the same crate that defines it

Solution: Binary crates cannot define and use proc-macros. Create a separate macros crate in your workspace (see Scenario 2 above).

Re-exports§

pub use validation::CrateType;
pub use validation::detect_crate_type;
pub use validation::validate_crate_type_for_macros;

Modules§

codegen
Proc-macro code generation.
includes
Cross-file include resolution.
parser
TOML configuration parser.
prelude
Common imports for convenience.
resolver
Definition resolver with inheritance support.
validation
Validation utilities for derive-defs configuration.

Enums§

Error
Errors that can occur during code generation.

Functions§

generate
Generate proc-macro code from a TOML configuration file.
generate_from_resolved
Generate proc-macro code from an already resolved configuration.
generate_to
Generate proc-macro code from a TOML configuration file with custom output path.

Type Aliases§

Result
Result type alias for this crate.