rust-dix 0.6.0

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
<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://gitcode.com/rf2026/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.6"
```

---

### 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
let provider = ServiceCollection::new()
    .singleton(|_| Arc::new(Logger::new("app")))
    .singleton(|_| Arc::new(Cache::new(1024)))
    .transient(|p| Arc::new(MyService::new(p.get().unwrap(), p.get().unwrap())))
    .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 {
    ServiceCollection::new()
        .singleton(|_| Arc::new(MockDatabase::new()))
        .transient(|p| Arc::new(MyApi::new(p.get().unwrap())))
        .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.
- **`IServiceLocator`**: pass a unified DI interface 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 |
| `.singleton_value(v)` | Singleton | Register a plain value (wraps in `Arc`) |
| `.add(lt, f)` | *any* | Specify lifetime explicitly |

The factory closure `f` receives `&dyn IServiceResolver` so you can resolve
dependencies:

```rust
ServiceCollection::new()
    .singleton(|_| Arc::new(Pool::new()))
    .transient(|p| Arc::new(Repo::new(p.get::<Pool>().unwrap())))
    .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 `__rdi_construct_T` |
| `#[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; use `.unwrap()` for required services |
| `.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 registered instances (keyed + unkeyed) |
| `.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; use `.unwrap()` for transient/scoped |
| `.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 |
| `.scope()` | `Scope` | New scope for scoped services |

`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();
```

---

### 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 ✓ |
| **Singleton** | Panics (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` panics, `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.
- 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 services, you can still use `get()`. The async methods are only needed
for services registered with `async_*`.

#### 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
    .scoped(|r| {                                     // sync OK
        let db = r.get::<DbPool>().unwrap();
        Arc::new(UserRepository { db })
    })
    .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| {
        let db = r.get::<DbPool>().unwrap();
        Arc::new(UserRepository { db })
    })
    .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
}
```

---

### Flexible Application Patterns


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


```rust
let provider = ServiceCollection::new()
    .singleton(|_| Arc::new(DbPool::new(&config)))
    .transient(|p| Arc::new(UserRepo::new(p.get::<DbPool>().unwrap())))
    .transient(|p| Arc::new(UserService::new(p.get::<UserRepo>().unwrap())))
    .transient(|p| Arc::new(UserController::new(p.get::<UserService>().unwrap())))
    .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│ │ .create_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.