<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.
| *(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**:
| `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.
| `#[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.