rust-dix-macros 0.6.0

Procedural macros for rust-dix — a Rust dependency injection framework
Documentation
<p align="center">
  <img src="../assets/logo.svg" alt="rust-dix" width="80">
</p>

<h1 align="center">rust-dix-macros · Compile-time DI Code Generation</h1>

<p align="center">
  <a href="https://crates.io/crates/rust-dix-macros"><img alt="Crates.io" src="https://img.shields.io/crates/v/rust-dix-macros.svg?style=flat-square"></a>
  <a href="https://docs.rs/rust-dix-macros"><img alt="Docs" src="https://img.shields.io/docsrs/rust-dix-macros?style=flat-square"></a>
  <a href="https://opensource.org/licenses/MIT"><img alt="License" src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square"></a>
</p>

<p align="center">
  <a href="https://gitcode.com/rf2026/rust-dix/blob/main/crates/macros/README.zh.md"><strong>中文</strong></a>
</p>

`rust-dix-macros` provides procedural macros for the [`rust-dix`](../core/) DI
framework. These macros shift work from runtime to compile time, eliminating
boilerplate and letting you declare DI configuration as close to your types
as possible.

```toml
[dependencies]
rust-dix = "0.6"       # re-exports all macros — you usually don't need rust-dix-macros directly
```

> **Note**: You rarely need to add `rust-dix-macros` as a direct dependency.
> The `rust-dix` crate re-exports all macros (`Inject`, `module`, `inject`, `register`).
> Just `use rust_dix::*` and you're set.

---

### Macros


#### `#[rust_dix::inject]` — Attribute-based auto-registration (recommended)


**What it does**: Combines constructor generation and service registration in
one attribute. The attribute can be placed on **either a struct or a trait
impl block**, with a single optional lifetime argument that defaults to
`singleton`.

`ServiceCollection::from_injected()` collects all registrations at startup.

**Placement 1 — on a struct** (registers as concrete type):

```rust
use rust_dix::*;

#[rust_dix::inject]          // singleton by default

struct Config;

#[rust_dix::inject(transient)]

struct Worker { dep: Arc<Config> }
```

**Placement 2 — on a trait impl** (registers as `dyn Trait`, auto-detected):

```rust
trait UserRepo { /* ... */ }

#[rust_dix::inject(transient)]   // on the struct first

struct PgUserRepo { db: Arc<DbPool> }

#[rust_dix::inject]              // then on the impl — registers as `dyn UserRepo`

impl UserRepo for PgUserRepo { /* ... */ }
```

The trait type is read from `impl Trait for Type`, so there is **no need** to
spell out `as = dyn Trait` — the macro infers it. The impl-block form reuses
the constructor generated by the struct-level annotation (or by an explicit
`#[derive(Inject)]`), so you can register the same type under as many trait
interfaces as you like:

```rust
#[rust_dix::inject]

struct BootLoader;

#[rust_dix::inject] impl EventHandler for BootLoader { /* ... */ }
#[rust_dix::inject] impl StartupTask   for BootLoader { /* ... */ }
```

**Supported syntax**:

| Attribute | Placement | Registers as |
|-----------|-----------|--------------|
| `#[rust_dix::inject]` | struct | Concrete type, Singleton (default) |
| `#[rust_dix::inject(singleton)]` | struct | Concrete type, Singleton |
| `#[rust_dix::inject(scoped)]` | struct | Concrete type, Scoped |
| `#[rust_dix::inject(transient)]` | struct | Concrete type, Transient |
| `#[rust_dix::inject]` | `impl Trait for T` | `dyn Trait`, Singleton (default) |
| `#[rust_dix::inject(scoped)]` | `impl Trait for T` | `dyn Trait`, Scoped |
| `#[rust_dix::inject(transient)]` | `impl Trait for T` | `dyn Trait`, Transient |

Supports both named-field structs and unit structs (zero fields):

```rust
#[rust_dix::inject]
struct UnitService;  // unit struct — works!

#[rust_dix::inject(transient)]
struct NamedService {
    dep: Arc<UnitService>,
}
```

This macro subsumes `#[derive(Inject)]` — when placed on a struct, it
internally generates the same constructor function
(`__rdi_construct_{Type}`) using the same field attribute syntax
(`#[inject]`, `#[inject(owned)]`, `#[inject(key = "...")]`, etc.). You do
NOT need both. The impl-block placement does **not** generate a constructor —
it reuses the one already produced by the struct placement.

> **Migration note**: The old verbose syntax
> `#[rust_dix::inject_attr(singleton, as = dyn Trait<...>)]` has been
> removed. Use the impl-block placement instead.

---

#### `#[derive(Inject)]` — Auto-generated constructor


**What it does**: Reads the struct fields and their `#[inject(...)]` attributes,
then generates a factory function `__rdi_construct_{TypeName}()` that resolves
each marked field from the container. Unmarked fields use `Default::default()`.

> **Note**: This derive macro is subsumed by `#[rust_dix::inject]` on a
> struct. You only need `#[derive(Inject)]` if you want to generate the
> constructor without automatic registration (e.g., to register manually
> with a custom factory closure).

**Without it**, you write factory closures manually:

```rust
let provider = ServiceCollection::new()
    .singleton(|p| {
        Arc::new(MyService::new(
            p.get::<Logger>(),
            p.get_keyed::<Cache>("main"),
        ))
    })
    .build().unwrap();
```

**With it**, the macro generates the exact same code:

```rust
#[derive(Inject)]

struct MyService {
    #[inject]
    logger: Arc<Logger>,
    #[inject(key = "main")]
    cache: Arc<Cache>,
}
// Generates: __rdi_construct_MyService(resolver) -> Arc<MyService>
```

The generated function can be used anywhere you'd write a factory closure:

```rust
let provider = ServiceCollection::new()
    .singleton(__rdi_construct_MyService)
    .build().unwrap();
```

**Field attributes** (`#[inject(...)]`):

Explicit injection: only marked fields consult the resolver. Unmarked fields
use `Default::default()` and are treated as internal state. Optionality is
driven by the field type (`Option<...>`), not by a marker.

| Attribute | Field type | Behavior |
|-----------|-----------|----------|
| *(none)* | any (`Default`) | Internal field — uses `Default::default()` |
| `#[inject]` | `Arc<T>` | Required shared — panics if `T` not registered |
| `#[inject]` | `Option<Arc<T>>` | Optional shared — `None` if `T` not registered |
| `#[inject(key = "k")]` | `Arc<T>` | Keyed shared, panics if key missing |
| `#[inject(key = "k")]` | `Option<Arc<T>>` | Optional keyed shared |
| `#[inject(owned)]` | bare `T` | Required owned — panics if not registered or Singleton |
| `#[inject(owned)]` | `Option<T>` | Optional owned — `None` if not registered or Singleton |
| `#[inject(owned, key = "k")]` | bare `T` / `Option<T>` | Keyed owned |
| `#[inject]` | `Vec<Arc<T>>` | Polymorphic — collects all registered implementations (default + keyed); key not supported |
| `#[inject(provider)]` | `Arc<ServiceProvider>` | Injects the provider for on-demand resolution |

> Type checking is strict: `#[inject]` accepts `Arc<T>`, `Option<Arc<T>>`, or
> `Vec<Arc<T>>`, `#[inject(owned)]` only accepts bare `T` / `Option<T>`.
> Mismatched markers produce a compile-time error.

---

#### `#[rust_dix::module]` — Compile-time module scanning


**What it does**: Scans a `mod` block for `rust_dix::register!()` declarations at
compile time, then generates a `__rdi_build_provider_{module_name}()` function
that constructs a fully configured `ServiceProvider`.

**Without it**, you register services one by one in a central setup function:

```rust
fn build_provider() -> ServiceProvider {
    ServiceCollection::new()
        .singleton(|_| Arc::new(MyService::default()))
        .singleton(|_| Arc::new(MyPlugin::default()))
        .keyed_singleton("verbose", |_| Arc::new(Logger::default()))
        .build().unwrap()
}
```

**With it**, you declare registrations right inside the module:

```rust
#[rust_dix::module]

mod services {
    rust_dix::register!(singleton: MyService);
    rust_dix::register!(singleton: dyn IPlugin => MyPlugin);
    rust_dix::register!(keyed "verbose": singleton: Logger);
}

// Generates: services::__rdi_build_provider_services() -> Result<Arc<ServiceProvider>>
let provider = services::__rdi_build_provider_services().unwrap();
```

**Supported declarations**:

| Syntax | Meaning |
|--------|---------|
| `register!(singleton: T)` | Register `T` as singleton using `Default` |
| `register!(scoped: T)` | Register `T` as scoped using `Default` |
| `register!(transient: T)` | Register `T` as transient using `Default` |
| `register!(singleton: dyn Trait => Impl)` | Register `Impl` as singleton implementing `Trait` |
| `register!(keyed "k": singleton: T)` | Register `T` as keyed singleton |
| `register!(keyed "k": scoped: T)` | Register `T` as keyed scoped |
| `register!(factory singleton: T => expr)` | Register with a custom factory expression |

**When to use it**:

- You want service registrations physically close to the module that owns them
- You are building a library or plugin and want to ship a pre-configured
  provider builder
- You prefer a declarative DSL over method chaining

---

#### `rust_dix::register!` — The declaration macro


This is the macro used inside `#[rust_dix::module]` blocks to declare service
registrations. It expands to nothing at the item level — its only effect is
being collected by the enclosing `#[rust_dix::module]` attribute.

You can also use `register!` standalone as a `macro_export`:

```rust
rust_dix::register!(singleton: MyService);
```

> **Migration note**: The function-like macro was renamed from `inject!()` to
> `register!()` to avoid the proc-macro namespace conflict (Rust does not
> allow an attribute macro and a function-like macro to share the same name
> in the same crate). This freed up the name `inject` for the attribute macro
> described above.

---

### Relationship with rust-dix


`rust-dix-macros` is a companion to [`rust-dix`](../core/). You can use `rust-dix`
without any macros — just write factory closures manually — and everything
works. The macros exist to eliminate repetition when you have many services
or want to declare DI configuration declaratively.

| Approach | Pros | Cons |
|----------|------|------|
| `#[rust_dix::inject]` (recommended) | Zero boilerplate, auto-registration, impl-block trait inference | Requires `inventory`; unmarked fields need `Default` |
| Manual factories | Full control, explicit | Boilerplate for many services |
| `#[derive(Inject)]` | Zero boilerplate for constructors | Manual registration still needed |
| `#[rust_dix::module]` | Declarative, centralized | Less flexible for dynamic registration |

All approaches can be mixed freely in the same project.

---

### License


MIT.