rust-dix-macros provides procedural macros for the rust-dix 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.
[]
= "0.6" # re-exports all macros — you usually don't need rust-dix-macros directly
Note: You rarely need to add
rust-dix-macrosas a direct dependency. Therust-dixcrate re-exports all macros (Inject,module,inject,register). Justuse 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):
use *;
// singleton by default
;
Placement 2 — on a trait impl (registers as dyn Trait, auto-detected):
// on the struct first
// then on the impl — registers as `dyn UserRepo`
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:
;
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):
; // unit struct — works!
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:
let provider = new
.singleton
.build.unwrap;
With it, the macro generates the exact same code:
// Generates: __rdi_construct_MyService(resolver) -> Arc<MyService>
The generated function can be used anywhere you'd write a factory closure:
let provider = new
.singleton
.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]acceptsArc<T>,Option<Arc<T>>, orVec<Arc<T>>,#[inject(owned)]only accepts bareT/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:
With it, you declare registrations right inside the module:
// Generates: services::__rdi_build_provider_services() -> Result<Arc<ServiceProvider>>
let provider = __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:
register!;
Migration note: The function-like macro was renamed from
inject!()toregister!()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 nameinjectfor the attribute macro described above.
Relationship with rust-dix
rust-dix-macros is a companion to rust-dix. 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.