rust-dix 0.8.1

rust-dix: A Rust dependency injection framework inspired by Microsoft.Extensions.DependencyInjection
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
<p align="center">
  <img src="../assets/logo.svg" alt="rust-dix" width="80">
</p>

<h1 align="center">rust-dix · Dependency Injection Container</h1>

<p align="center">
  <a href="https://crates.io/crates/rust-dix"><img alt="Crates.io" src="https://img.shields.io/crates/v/rust-dix.svg?style=flat-square"></a>
  <a href="https://docs.rs/rust-dix"><img alt="Docs" src="https://img.shields.io/docsrs/rust-dix?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://github.com/Rust-Framework/rust-dix/blob/main/crates/core/README.zh.md"><strong>中文</strong></a>
</p>

`rust-dix` is the core crate of the rust-dix framework — a Rust dependency injection
container inspired by Microsoft.Extensions.DependencyInjection (MEDI).

```toml
[dependencies]
rust-dix = "0.8"
```

---

### When should you use this?

**1. You want to decouple service creation from usage**

Instead of `MyService::new(dep1, dep2, dep3)` scattered across your codebase,
you declare once how services are constructed and let the container wire them
up automatically.

```rust
// Without DI: every call site must know how to build everything
let svc = MyService::new(
    Arc::new(Logger::new("app")),
    Arc::new(Cache::new(1024)),
);

// With DI: declare once, resolve anywhere.
// `MyService` is annotated with `#[rust_dix::inject(transient)]`, so it is
// registered automatically (its definition is omitted here).
let provider = ServiceCollection::from_injected()
    .singleton(|_| Arc::new(Logger::new("app")))
    .singleton(|_| Arc::new(Cache::new(1024)))
    .build()
    .unwrap();

let svc: Arc<MyService> = provider.get().unwrap();
```

**2. You need to swap implementations for testing**

Change one registration line — no need to touch the code that consumes the
service.

```rust
#[cfg(test)]
fn test_provider() -> ServiceProvider {
    // `MyApi` is annotated with `#[rust_dix::inject(transient)]` and collected by
    // `from_injected()`; only the mock has to be registered here.
    ServiceCollection::from_injected()
        .singleton(|_| Arc::new(MockDatabase::new()))
        .build()
        .unwrap()
}
```

**3. You have objects with different lifetimes**

Some services should be shared globally (database pool), some per-request
(HTTP context), some created fresh each time (value objects). rust-dix gives you
three lifetimes to model this naturally.

| Lifetime | Behavior | Typical use |
|----------|----------|-------------|
| **Singleton** | Created once, shared everywhere | Database pool, config, event bus |
| **Scoped** | Created once per scope, shared within scope | HTTP request context, unit of work, transaction |
| **Transient** | Created every time you resolve it | Value objects, DTOs, lightweight stateless services |

```rust
let provider = ServiceCollection::new()
    .singleton(|_| Arc::new(Pool::new()))           // one pool for the app
    .scoped(|_| Arc::new(RequestContext::new()))     // one per request
    .transient(|_| Arc::new(Query::new()))           // new each time
    .build()
    .unwrap();
```

**4. You want multiple instances of the same type**

Keyed services let you register several implementations of the same trait,
each distinguished by a string key. All three lifetimes are supported:

```rust
// Keyed with different lifetimes
let provider = ServiceCollection::new()
    // Singleton keyed - shared globally
    .keyed_singleton::<dyn Strategy>("fast", |_| Arc::new(FastPath))
    // Scoped keyed - shared within a scope
    .keyed_scoped::<dyn Strategy>("session", |_| Arc::new(SessionStrategy))
    // Transient keyed - new instance each time
    .keyed_transient::<dyn Strategy>("fresh", |_| Arc::new(FreshStrategy))
    .build()
    .unwrap();

let fast: Arc<dyn Strategy> = provider.get_keyed("fast").unwrap();
	let session: Arc<dyn Strategy> = provider.scope().get_keyed("session").unwrap();
	let fresh1: Arc<dyn Strategy> = provider.get_keyed("fresh").unwrap();
	let fresh2: Arc<dyn Strategy> = provider.get_keyed("fresh").unwrap();  // different instance
```

**5. You are building a plugin / modular system**

- **Layered containers**: child-first, root-fallback resolution for plugin
  isolation. The plugin sees its own services first; host services are visible
  as fallback.
- **Named services**: register services by string name — critical for cdylib
  plugins where Rust's `TypeId` differs across compilation units.
- **`IProvider`**: pass a unified DI interface (resolution + named registry) to
  external modules that don't depend on rust-dix directly.

---

### Quick Reference · API

### Registration (`ServiceCollection`)

| Method | Lifetime | Use when |
|--------|----------|----------|
| `.from_injected()` | *mixed* | Collect all `#[rust_dix::inject]` annotations (see below) |
| `.singleton(f)` | Singleton | You need exactly one instance shared globally |
| `.scoped(f)` | Scoped | You need one instance per scope (request/transaction) |
| `.transient(f)` | Transient | You need a new instance every time |
| `.keyed_singleton(k, f)` | Singleton | Multiple named instances (strategy pattern) |
| `.keyed_scoped(k, f)` | Scoped | Named instances scoped per request |
| `.keyed_transient(k, f)` | Transient | Named instances created fresh each time |
| `.instance(arc)` | Singleton | You already have a built `Arc<T>` |
| `.try_add(f)` | Singleton | Only register if not already present (MEDI `TryAdd`) |
| `.try_add_scoped(f)` | Scoped | Only register if not already present |
| `.try_add_transient(f)` | Transient | Only register if not already present |
| `.singleton_value(v)` | Singleton | Register a plain value (wraps in `Arc`) |
| `.add(lt, f)` | *any* | Specify lifetime explicitly |
| `.build()` | — | Build the provider with default (strict) scope validation |
| `.build_with(options)` | — | Build with explicit `ServiceProviderOptions` (`lenient()` restores 0.7.x root-scope behavior) |
| `.build_async()` / `.build_async_with(options)` | — | Async build; required when any `async_*` registration exists |

The factory closure `f` receives `&dyn IServiceResolver`. Generic `get::<T>()`
requires `Self: Sized`, so it cannot be called on that trait object: let the
inject macros generate the constructor (internal `__rdi_construct_*`,
`#[doc(hidden)]`), or resolve with `get_any` + downcast:

```rust
ServiceCollection::new()
    .singleton(|_| Arc::new(Pool::new()))
    // `Repo` is `#[derive(Inject)]` with `#[inject] pool: Arc<Pool>`; the
    // generated constructor resolves its fields during registration.
    .transient(|r| __rdi_construct_Repo(r))
    .build()
    .unwrap();
```

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

The preferred way to register services is to annotate structs (or trait impl
blocks) directly:

```rust
use rust_dix::*;

// Annotate a struct → registered as its concrete type.
// Lifetime defaults to `singleton` when omitted.
#[rust_dix::inject]
struct Config { url: String }

#[rust_dix::inject(transient)]
struct Worker { logger: Arc<Config> }

// Annotate a trait impl → registered as `dyn Trait`.
// The trait type is auto-detected from `impl Trait for Type`,
// so no `as = dyn Trait` boilerplate is needed.
trait UserRepo { /* ... */ }

// For trait-oriented services, use #[derive(Inject)] on the struct (generates
// a constructor but does NOT register) and #[inject] on the impl block only.
// Putting #[inject] on BOTH struct and impl causes double registration.
#[derive(Inject)]
struct PgUserRepo { db: Arc<DbPool> }

#[rust_dix::inject]              // on the impl: trait registration (singleton by default)
impl UserRepo for PgUserRepo { /* ... */ }

// Then build from all annotations in one call:
let provider = ServiceCollection::from_injected().build().unwrap();
```

The `#[rust_dix::inject]` attribute automatically generates constructor code
(like `#[derive(Inject)]`) and registers the service via `inventory`
(on Linux, macOS, and Windows).
Supports both named-field structs and unit structs (zero fields):

```rust
// Unit struct — zero fields, works out of the box
#[rust_dix::inject]
struct RoleAuthorizer;

impl Default for RoleAuthorizer { fn default() -> Self { Self } }
```

**Two placement sites, one consistent syntax:**

| Placement | What it registers | Reuses constructor? |
|-----------|-------------------|---------------------|
| `#[inject]` on struct | Concrete type `T` | Generates its own |
| `#[inject]` on `impl Trait for T` | `dyn Trait` | Reuses the generated constructor (internal `__rdi_construct_T`, `#[doc(hidden)]`) |
| `#[derive(Inject)]` on struct | *(nothing — constructor only)* | — |

**Choose one site, don't double-register:** `#[inject]` generally goes on
either the struct *or* the impl block, not both. Putting `#[inject]` on both
registers the same struct twice (concrete type + `dyn Trait`), which is rarely
intended — consumers can then bypass the trait via `get::<ConcreteType>()`.
For trait-oriented services (handlers, services, repositories), prefer
`#[derive(Inject)]` on the struct (constructor only, no registration) plus
`#[inject]` on the impl block. This replaces the old `as = dyn Trait` /
`as = [dyn A, dyn B]` syntax.

#### Resolution (`ServiceProvider` / `Scope`)

| Method | Returns | Behavior |
|--------|---------|----------|
| `.get::<T>()` | `Result<Arc<T>, RdiError>` | Returns Err if not registered; **last registration wins** |
| `.get_optional::<T>()` | `Option<Arc<T>>` | Returns `None` if not registered |
| `.get_keyed::<T>(key)` | `Result<Arc<T>, RdiError>` | Returns Err if key not found; use `.unwrap()` for required keys |
| `.try_get_keyed::<T>(key)` | `Option<Arc<T>>` | Returns `None` if key not found |
| `.get_all::<T>()` | `Vec<Arc<T>>` | All **non-keyed** registrations, in registration order (keyed excluded) |
| `.get_all_keyed::<T>()` | `Vec<Arc<T>>` | All **keyed** registrations of `T`, in registration order (0.8.0+) |
| `.get_named::<T>(name)` | `Option<Arc<T>>` | Named resolution (cross-DLL), returns `None` if missing |
| `.get_named_any::<T>(name)` | `Option<Arc<T>>` | Named, returns `None` if missing |
| `.get_owned::<T>()` | `Result<T, RdiError>` | Owned instance; Singleton returns Err; **Scoped is allowed from the root** (caller-owned, never cached — its Scoped dependencies still obey scope validation) |
| `.try_get_owned::<T>()` | `Option<T>` | `None` if unregistered or Singleton |
| `.get_keyed_owned::<T>(key)` | `Result<T, RdiError>` | Owned keyed instance; Singleton returns Err |
| `.is_registered::<T>()` | `bool` | Whether `T` has a non-keyed registration (does not instantiate) |
| `.is_registered_keyed::<T>(key)` | `bool` | Whether `T` is registered under `key` |
| `.scope()` | `Scope` | New scope for scoped services |
| `.dispose()` | `()` | Idempotent; releases instances in reverse creation order, later resolution returns `Err(ProviderDisposed)` (0.8.0+) |

> **0.8.x semantics**: single-service resolution is MEDI-style "last registration
> wins"; `get_all` is unkeyed-only (use `get_all_keyed` for keyed); and scope
> validation is **strict by default** — resolving a Scoped service from the root
> provider returns `Err(RdiError::ScopedFromRootProvider)` (the exception is
> `get_owned` / `try_get_owned`: the owned instance is caller-owned and not cached,
> while its Scoped dependencies still obey validation).
> Build with `build_with(ServiceProviderOptions::lenient())` to keep the 0.7.x
> root-scope behavior. See the [MEDI differences page]../../docs/rust-dix/01-introduction/medi-differences.md.

`T` can be a concrete type or `dyn Trait`:

```rust
let svc: Arc<MyService> = provider.get().unwrap();
let plugin: Arc<dyn IPlugin> = provider.get().unwrap();
let all: Vec<Arc<dyn IPlugin>> = provider.get_all();
```

#### Container injection (`ServiceProvider` / `ScopeFactory`)

The container is itself resolvable as a built-in service (no registration
required) through every resolver path — `provider.get()`, `#[inject]` fields and
build-time validation alike:

| Field | Notes |
|-------|-------|
| `#[inject(provider)] provider: Arc<ServiceProvider>` | On-demand resolution; also available in eagerly built singletons. **Warning:** a singleton holding this creates a strong `container → singleton → container` cycle; prefer `ScopeFactory` |
| `#[inject] provider: Arc<ServiceProvider>` | Same built-in, plain form (MEDI `IServiceProvider` parity); same strong-reference caveat |
| `#[inject] scope_factory: Arc<dyn ScopeFactory>` | Recommended for singleton workers: one scope per task. Weak-backed singleton, so holding it does not create an `Arc` cycle |

Explicit `singleton::<dyn ScopeFactory>(...)` registrations still take precedence
over the built-in.

> Resolving services *through an injected provider* inside a singleton
> constructor is rejected at build time with a typed `FactoryPanic`: it would
> bypass cycle/captive validation and make the result order-dependent. Store the
> handle and resolve lazily in methods.

---

### Owned service resolution (`&mut self` without interior mutability)

`get::<T>()` returns `Arc<T>`, giving you only `&T` — you cannot call
`&mut self` methods. For services that need mutable access (e.g. a
`DbContext` with `save_changes(&mut self)`), use `get_owned::<T>()` to
obtain an owned `T` instead of resorting to `RwLock` + `unsafe`:

```rust
struct DbContext { count: u32 }
impl DbContext {
    fn add(&mut self) { self.count += 1; }
    fn total(&self) -> u32 { self.count }
}

let provider = ServiceCollection::new()
    .transient(|_| Arc::new(DbContext { count: 0 }))
    .build()
    .unwrap();

let mut ctx: DbContext = provider.get_owned().unwrap();
ctx.add();                 // ✓ &mut self — no RwLock, no unsafe
assert_eq!(ctx.total(), 1);
```

| Lifetime | `get_owned` behavior |
|----------|----------------------|
| **Transient** | New instance each call ✓ |
| **Scoped** | Bypasses cache, new instance each call ✓ (callable from the root too: the owned instance is not subject to root scope validation, but its Scoped dependencies are) |
| **Singleton** | `Err(OwnedNotSupported)` (shared instance cannot be owned); use `try_get_owned` for `None` |

**With `#[derive(Inject)]`**: mark bare `T` fields with `#[inject(owned)]`
and `Arc<T>` fields with `#[inject]`. `Option<T>` → `try_get_owned`,
`Option<Arc<T>>` → `try_get`:

```rust
#[derive(Inject)]
struct Handler {
    #[inject(owned)]
    ctx: DbContext,           // owned  → get_owned
    #[inject]
    logger: Arc<Logger>,      // shared → get
}
// ...
let mut h: Handler = provider.get_owned().unwrap();
h.ctx.add();                 // ✓ owned field is mutable
```

**Limitations**:
- Singleton cannot be owned — `get_owned` returns `Err`, `try_get_owned` returns `None`.
- Scoped `get_owned` bypasses the scope cache (each call is fresh). This is
  intentional: owned `&mut self` is incompatible with scope-shared semantics.
  Owned resolution is the exception to strict scope validation: `get_owned` /
  `try_get_owned` may resolve a Scoped service from the **root** (the instance is
  caller-owned, never cached or tracked) — but Scoped dependencies resolved inside
  its constructor still return `Err(ScopedFromRootProvider)` at the root.
- Trait bound remains `T: Send + Sync + 'static` (reuses the existing factory,
  no separate owned-factory path).

---

### Async support (`build_async`)

For services that need async initialization (DB connections, network I/O, config
loading), use `build_async()` and the `async_*` registration methods.

#### Registration

| Method | Lifetime | Use when |
|--------|----------|----------|
| `.async_singleton(f)` | Singleton | Async init, shared globally (DB pool, Redis) |
| `.async_scoped(f)` | Scoped | Async init, per scope (request-scoped resources) |
| `.async_transient(f)` | Transient | Async init, fresh each time |
| `.async_keyed_singleton(k, f)` | Singleton keyed | Async init, named instance |
| `.async_keyed_scoped(k, f)` | Scoped keyed | Async init, per-scope named instance |
| `.async_keyed_transient(k, f)` | Transient keyed | Async init, fresh named instance |

The async factory receives `Arc<ServiceProvider>` and returns a pinned future:

```rust
let provider = ServiceCollection::new()
    .async_singleton(|p: Arc<ServiceProvider>| Box::pin(async move {
        let config: Arc<AppConfig> = p.get().unwrap();
        Arc::new(DbPool::connect(&config.conn_str).await)
    }))
    .async_keyed_singleton::<dyn IPaymentGateway>("wechat", |_| Box::pin(async {
        Arc::new(WechatPay::init().await) as Arc<dyn IPaymentGateway>
    }))
    .build_async()
    .await
    .unwrap();
```

#### Resolution

Use `get_async()` / `get_keyed_async()` when resolving async-registered services:

```rust
let pool: Arc<DbPool> = provider.get_async().await.unwrap();
let gateway: Arc<dyn IPaymentGateway> = provider.get_keyed_async("wechat").await.unwrap();
```

For sync-registered services you can use either `get()` or `get_async()`: `get_async()`
falls back to the synchronous resolution path for services registered with the sync
`scoped` / `transient` builders (0.8.0+). Async registration (`async_*`) is only needed
for services that require **asynchronous initialization**.

Async singleton initialization is **dependency-first and registration-order independent**
(0.8.1+): a factory may `await` any async singleton, and `build_async()` recursively
initializes dependencies first (deterministic dependency-first topological order, eager and
fail-fast). Dependency cycles return `RdiError::CircularDependency` instead of dead-locking.

#### Mixing sync and async

You can mix sync and async registrations in the same collection. Sync singletons
are validated and initialized alongside async ones during `build_async()`:

```rust
let provider = ServiceCollection::new()
    .async_singleton(|_| Box::pin(async { Arc::new(DbPool::new()) }))
    .singleton(|_| Arc::new(AppConfig::new()))       // sync OK
    // `UserRepository` is `#[derive(Inject)]`; its constructor resolves DbPool.
    .scoped(|r| __rdi_construct_UserRepository(r))   // sync OK
    .build_async()
    .await
    .unwrap();
```

**Key rule**: Once you use any `async_*` registration, call `build_async()`
instead of `build()`. Mixing is supported — sync singletons are validated and
initialized alongside async ones.

#### Per-request scope with async

```rust
let provider = ServiceCollection::new()
    .async_singleton(|_| Box::pin(async { Arc::new(DbPool::new()) }))
    .scoped(|r| __rdi_construct_UserRepository(r))
    .build_async().await.unwrap();

// Each request gets its own scope
fn handle_request(provider: &Arc<ServiceProvider>) {
    let scope = provider.scope();
    let repo: Arc<UserRepository> = scope.get().unwrap();
    // ... process request ...
    // scope dropped → scoped instances released (reverse creation order)
}
```

---

### Flexible Application Patterns

#### 🔹 Three-layered architecture (Controller → Service → Repository)

```rust
// `UserRepo` / `UserService` / `UserController` are `#[derive(Inject)]`; the
// generated constructors wire the dependencies for each layer.
let provider = ServiceCollection::new()
    .singleton(|_| Arc::new(DbPool::new(&config)))
    .transient(|r| __rdi_construct_UserRepo(r))
    .transient(|r| __rdi_construct_UserService(r))
    .transient(|r| __rdi_construct_UserController(r))
    .build()
    .unwrap();
```

#### 🔹 Strategy pattern with keyed services

```rust
let provider = ServiceCollection::new()
    .keyed_singleton::<dyn PaymentGateway>("credit", |_| Arc::new(CreditCardGateway))
    .keyed_singleton::<dyn PaymentGateway>("alipay", |_| Arc::new(AlipayGateway))
    .keyed_singleton::<dyn PaymentGateway>("wechat", |_| Arc::new(WechatGateway))
    .build()
    .unwrap();

fn checkout(provider: &ServiceProvider, method: &str) {
    let gateway: Arc<dyn PaymentGateway> = provider.get_keyed(method).unwrap();
    gateway.charge(100);
}
```

#### 🔹 Scoped per-request (web server)

```rust
fn handle_request(container: &ServiceProvider) {
    let scope = container.scope();
    let ctx: Arc<RequestContext> = scope.get().unwrap();
    let svc: Arc<MyService> = scope.get().unwrap();
    // ctx and MyService share the same scope — scoped services are cached
    // Drops when scope goes out of scope
}
```

#### 🔹 Plugin isolation with ServiceProviderWrapper

```rust
let host_provider = ServiceCollection::new()
    .singleton(|_| Arc::new(HostService::new()))
    .build().unwrap();

let plugin_provider = ServiceCollection::new()
    .singleton(|_| Arc::new(PluginService::new()))
    .build().unwrap();

let wrapper = ServiceProviderWrapper::new(plugin_provider, host_provider);
// PluginService resolved from plugin container
// HostService falls back to host container
```

#### 🔹 Cross-DLL plugin with named services

```rust
// Host process
let provider = ServiceCollection::new()
    .singleton(|_| Arc::new(EventBus::new()))
    .build().unwrap();
provider.register_named("event_bus", provider.get::<EventBus>().unwrap());

// Plugin loaded from cdylib (separate compilation unit)
let bus = host.get_named::<EventBus>("event_bus");
```

#### 🔹 External system integration via IProvider

```rust
let provider: Arc<dyn IProvider> = provider as Arc<dyn IProvider>;
// Pass to third-party code or FFI boundary
```

---

### Architecture

```
┌──────────────────────────────────────────────┐
│               ServiceCollection               │
│  ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│  │singleton │ │  scoped  │ │  transient   │ │
│  │.keyed_singleton│ │.keyed_*()│ │ .instance()  │ │
│  └──────────┘ └──────────┘ └──────────────┘ │
│                   │ .build()                  │
│                   ▼                           │
│              ServiceProvider                  │
│  ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│  │  .get()  │ │.get_keyed│ │  .scope()   │ │
│  │ .get_all │ │.get_named│ │             │ │
│  └──────────┘ └──────────┘ └──────┬───────┘ │
│                                    │         │
└────────────────────────────────────┼─────────┘
                                     │
                                     ▼
                               ┌──────────┐
                               │   Scope  │
                               │ .get()   │
                               │ scoped   │
                               │ cached   │
                               └──────────┘
```

---

### Relationship with rust-dix-macros

`rust-dix` works with or without [`rust-dix-macros`](../macros/). The macros
provide three compile-time conveniences:

- **`#[rust_dix::inject(...)]`** (recommended) — Attribute macro that combines
  constructor generation + auto-registration. One annotation on a struct (or
  trait impl) is all you need — `ServiceCollection::from_injected()` collects
  everything.
- **`#[derive(Inject)]`** — Generate the factory function automatically from
  struct fields. Used internally by `#[rust_dix::inject]`.
- **`#[rust_dix::module]`** — Collect `rust_dix::register!()` declarations at compile
  time and generate a complete provider builder. Useful for external types,
  conditional compilation, and centralized management.

See [`macros/README.md`](../macros/README.md) for details.

---

### License

MIT.