rust-dix 0.10.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
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
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
<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.10"
```

> **MSRV**: Rust **1.95**. Both crates are `#![forbid(unsafe_code)]`; CI runs a
> test matrix plus RustSec `cargo audit`. See `SECURITY.md` at the repository root.

---

### 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 key — a string or a typed `ServiceKey` value. 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))
    // AnyKey fallback: used when no exact key is registered (cached per (registration, requested key))
    .keyed_any_singleton::<dyn Strategy>(|_, _key| Arc::new(DefaultStrategy))
    .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
// Typed keys work too: provider.get_keyed(ServiceKey::new(StrategyKey::Fast))
```

**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 keyed instances (strategy pattern); `k: impl Into<ServiceKey>` |
| `.keyed_scoped(k, f)` | Scoped | Keyed instances scoped per request |
| `.keyed_transient(k, f)` | Transient | Keyed instances created fresh each time |
| `.keyed_any_singleton(f)` | Singleton | `AnyKey` fallback (MEDI parity); `f: Fn(&dyn IServiceResolver, &ServiceKey) -> Arc<T>`, cached per (registration, requested key) |
| `.keyed_any_scoped(f)` | Scoped | `AnyKey` fallback, one instance per scope per (registration, requested key) |
| `.keyed_any_transient(f)` | Transient | `AnyKey` fallback, fresh per resolution |
| `.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` (`ServiceProviderOptions::new()` / `Default` = strict; `with_validate_scopes(bool)`; `lenient()` restores 0.7.x root-scope behavior) |
| `.build_async()` / `.build_async_with(options)` | — | Async build; required when any `async_*` registration exists |

#### Typed keys (`ServiceKey`) and `AnyKey` fallback

Keyed APIs accept `impl Into<ServiceKey>`: strings (`&str` / `String`) keep working, and
`ServiceKey::new(value)` supports enum/integer/custom keys. Key equality includes the
value's type — `ServiceKey::new(1u32) != ServiceKey::from("1")`. `ServiceKey::any_key()` /
`KeyedService::any_key()` is the fallback sentinel and `ServiceKey::is_any()` detects it;
`ServiceKey::as_str()` returns `Some` for string-like keys (`String` / `&'static str`,
including `ServiceKey::new("literal")`) and `None` for enum/integer/custom typed keys and
the `AnyKey` sentinel. The default typed-key resolver methods fall back to the legacy string
path for `Some`, so a typed `&'static str` key is treated as a **`String` key** there (not
equal in fully typed containers) — typed consumers should use the `*_key` methods.
**String-key footgun**: `ServiceKey::new("x")` is a `&str`-typed key and is *not* equal to
the `String` key stored by `keyed_singleton("x", …)`; use `"x".into()` /
`ServiceKey::from("x")` for string keys and `ServiceKey::new` only for typed
(`&'static str` / enum / integer) keys.

```rust
use rust_dix::*;
use std::sync::Arc;

#[derive(Debug, PartialEq, Eq, Hash)]
enum DbKey { Read, Write }

let provider = ServiceCollection::new()
    .keyed_singleton::<dyn IDbConnection>(ServiceKey::new(DbKey::Read), |_| Arc::new(ReadPool))
    .keyed_any_singleton::<dyn IDbConnection>(|_, key| {
        // `key` is the requested ServiceKey (exact keys win over this fallback).
        Arc::new(Pool::connect(&format!("{key:?}")))
    })
    .build()
    .unwrap();

let read: Arc<dyn IDbConnection> = provider.get_keyed(ServiceKey::new(DbKey::Read)).unwrap();
```

`AnyKey` semantics (full details:
[current (0.10) semantics](https://github.com/Rust-Framework/rust-dix/blob/main/docs/rust-dix/09-reference/semantics.md#2-anykey-兜底)):
- exact-key registrations take precedence; the fallback is used only when no exact key exists;
- singleton/scoped fallback instances are cached **per (registration/type, requested key)**
  (different `AnyKey` registrations stay independent; scoped: per scope per (registration, key));
  a singleton consumed during build validation is prefilled into that cache and reused at runtime
  (single construction, identity preserved);
- `get_all_keyed` excludes fallbacks; the `any_key()` sentinel is invalid for single-service
  resolution (`is_registered_keyed(any_key()) == false`); registrations are sync-only and not
  eagerly initialized/validated at build;
- `get_keyed_owned`/`try_get_keyed_owned` for a scoped/transient fallback is allowed at the strict
  root (caller-owned exception); nested resolution of a **different** requested key is legal;
  the factory gets `&ServiceKey` (`Debug`/`Display`/`Eq`/`Hash`, no typed downcast accessor).

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

Type-erased `IServiceResolver` helpers used by the macros and by custom factories
(`get_any` / `get_keyed_any` panic on unresolvable, the `*_result` variants return `Err`):

| Method | Use |
|--------|-----|
| `get_any(type_name)` / `get_by_type_id(TypeId)` | Type-erased single lookup (String / TypeId path) |
| `get_keyed_any(type_name, variant)` / `get_keyed_by_type_id(TypeId, key)` | Type-erased keyed lookup |
| `get_all_any_result(type_name)` / `get_all_any(type_name)` | Type-erased `get_all` (`*_result` returns `Err` for uninitialized async) |
| `get_keyed_any_key(type_name, &ServiceKey)` / `try_get_keyed_any_result_key(...)` | **Typed-keyed** lookup (0.10); default falls back to string path only for `ServiceKey::as_str() == Some` |
| `get_keyed_owned_any_key(type_name, &ServiceKey)` / `try_get_keyed_owned_any_key(...)` | Typed-keyed owned lookup (0.10); same string-only default |
| `try_get_any_result(type_name)` / `try_get_keyed_any_result(type_name, variant)` | Infallible probing (no panic) |

> The four `*_key` methods have default implementations that only support string-like keys
> (`ServiceKey::as_str()`); `ServiceProvider` / `Scope` / `ServiceProviderWrapper` and the
> build-time validation resolver override them with full typed-key support (AnyKey fallback,
> owned, captive checks). Custom `IServiceResolver` implementations must override them to
> support non-string keys. Extension contract:
> [extension points]https://github.com/Rust-Framework/rust-dix/blob/main/docs/rust-dix/09-reference/extension-points.md.

#### `#[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 { #[inject] 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 { #[inject] 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>` | `key: impl Into<ServiceKey>`; exact key wins over an `AnyKey` fallback; `any_key()` is not a valid single-service key |
| `.try_get_keyed::<T>(key)` | `Option<Arc<T>>` | Returns `None` if key not found/unresolvable |
| `.get_all::<T>()` | `Vec<Arc<T>>` | All **non-keyed** registrations, in registration order (keyed excluded) |
| `.get_all_keyed::<T>()` | `Vec<Arc<T>>` | All **explicitly keyed** registrations of `T`, in registration order (`AnyKey` fallbacks excluded) |
| `.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 `OwnedNotSupported` |
| `.try_get_keyed_owned::<T>(key)` | `Option<T>` | `None` if unregistered / Singleton (infallible probing) |
| `.is_registered::<T>()` | `bool` | Whether `T` has a non-keyed registration (does not instantiate) |
| `.is_registered_keyed::<T>(key)` | `bool` | Whether `T` has an exact keyed registration or an `AnyKey` fallback (`any_key()` → `false`) |
| `.scope()` | `Scope` | New scope for scoped services |
| `.async_context()` | `Option<AsyncFactoryContext>` | Capture the current async chain for detached tasks (cross-task cycle detection) |
| `.dispose()` | `()` | Idempotent; releases instances in reverse creation order, later resolution returns `Err(ProviderDisposed)` (0.8.0 起,0.10.x 同样适用) |

> **0.10.x semantics (summary)**: last registration wins; `get_all` is unkeyed-only
> (`get_all_keyed` lists explicit keys, excluding `AnyKey` fallbacks); keys may be typed;
> scope validation is strict by default (root Scoped → `ScopedFromRootProvider`, with
> `get_owned`/keyed owned as the caller-owned exception). Full semantics:
> [docs/rust-dix/09-reference/semantics.md]https://github.com/Rust-Framework/rust-dix/blob/main/docs/rust-dix/09-reference/semantics.md.

> **Enumeration and async**: `get_all` / `get_all_keyed` / `#[inject] Vec<Arc<T>>` include
> already-initialized async singletons; uninitialized async services yield
> `AsyncServiceRequiresGetAsync` (enumeration panics/`Err` as applicable). Async
> scoped/transient services should be resolved item-by-item with `get_async`; async scoped
> enumerated at the root is omitted by scope validation (panics only inside a `Scope`).

#### Diagnostics

| Error | When |
|-------|------|
| `RdiError::SyncSingletonDependsOnAsyncService { singleton, async_service }` | a sync singleton depends on an async service (build time); make the dependent async or resolve lazily |
| `RdiError::AsyncServiceRequiresGetAsync { service }` | synchronous `get` of an async service (`get_optional` returns `None`), or synchronous enumeration of an uninitialized async service; use `get_async` |
| `RdiError::AsyncServiceRequiresBuildAsync { service }` | `build()` used on a collection containing `async_*` registrations; use `build_async()` |
| `RdiError::is_circular()` | **heuristic** helper: `true` for a direct `CircularDependency` or a `FactoryPanic` payload containing `CircularDependency` (custom payloads may false-positive; prefer matching the typed variant) |

`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 |
| `#[inject(key = <expr>)] field: Arc<T>` | Keyed injection; `expr: impl Into<ServiceKey>` (string literal, `ServiceKey::new(...)`, or downstream key via `From`) |
| `#[inject(owned)] field: T` | Owned (callback-owned) instance; `T` must be Transient/Scoped (Singleton → error) |
| `#[inject(owned, key = <expr>)] field: T` | Owned + keyed injection; same lifetime rules as `#[inject(owned)]` |

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, keyed instance (`k: impl Into<ServiceKey>`) |
| `.async_keyed_scoped(k, f)` | Scoped keyed | Async init, per-scope keyed instance |
| `.async_keyed_transient(k, f)` | Transient keyed | Async init, fresh keyed 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
(both accept string or typed `ServiceKey` keys and return `impl Future`; `.await` usage is
unchanged):

```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. Async registration (`async_*`) is only needed
for services that require **asynchronous initialization**. Synchronous `get` of an
async service reports `RdiError::AsyncServiceRequiresGetAsync`; `AnyKey` fallbacks are
sync-only and are also resolved through the synchronous path.

Async singleton initialization is **dependency-first and registration-order independent**:
a factory may `await` any async singleton, and `build_async()` recursively
initializes dependencies first (deterministic dependency-first topological order, eager and
fail-fast). A sync singleton depending on an async service is rejected at build time with
`RdiError::SyncSingletonDependsOnAsyncService` — make the dependent async or resolve
lazily.

#### Cross-task cycle detection (`AsyncFactoryContext`)

Async cycle detection is chain-based: each resolution future captures the current chain
when constructed, so self/cross recursion returns `RdiError::CircularDependency`. For
detached tasks, capture `sp.async_context()` inside the factory and resolve through the
cloned context; without the context a fully detached task may still dead-lock:

```rust
let provider = ServiceCollection::new()
    .async_singleton(|sp| Box::pin(async move {
        let ctx = sp.async_context().expect("built provider");
        let dep = tokio::spawn({
            let ctx = ctx.clone();
            async move { ctx.get_async::<DbPool>().await }
        })
        .await
        .expect("task")
        .expect("DbPool must resolve");
        Arc::new(Service { dep })
    }))
    .build_async()
    .await
    .unwrap();
```

`AsyncFactoryContext` exposes `get_async`, `get_keyed_async` and `provider()`, is `Clone`,
and can also be obtained from a `Scope` (`scope.async_context()`).

#### 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.