# Dependency injection (`di`)
**Compile-time** registration, thread-safe resolution via `Arc<T>`.
Single/trait bindings: **one macro per type**. Many/keyed: **one batch per macro call** → static `LazyLock<Arc<[...]>>` + trait impl (like legacy `components!`).
## Typical imports
```rust
use noema::core::{Container, Injectable, Resolver, arc_dyn};
use noema::{
resolve, Keyed, dependency, dependency_as, dependency_as_many, dependency_keyed_as,
};
```
Registrable types must implement `Injectable<Container>` (manually or via `#[derive(Injectable)]`).
---
## Registration: lifecycle × resolution mode
| `dependency!(lifecycle, Type)` | Concrete type | Per call (`singleton` / `transient`) |
| `dependency_as!(lifecycle, Trait: Impl)` | Trait → impl | Per call |
| `dependency_as_many!(Trait: [(lifecycle, Impl), …])` | Batch of impls in a “many” collection | **Per entry** |
| `dependency_keyed_as!(K, Trait: [(key, lifecycle, Impl), …])` | Batch keyed `K → Arc<dyn Trait>` | **Per entry** |
### Exact syntax
```rust
// ── Single (concrete) ─────────────────────────────────────────────
dependency!(singleton, MyService);
dependency!(transient, RequestId);
// ── Single (trait) ────────────────────────────────────────────────
// Trait name only; macro expands to dyn Trait + Send + Sync
dependency_as!(singleton, Logger: ConsoleLogger);
dependency_as!(transient, Logger: FileLogger);
// ── Many (batch in one macro call; lifecycle per entry) ───────────
dependency_as_many!(Validator: [
(singleton, EmailValidator),
(transient, SmsValidator),
]);
// ── Keyed (typed keys: K → Arc<dyn Trait>; lifecycle per entry) ─
#[derive(PartialEq)]
enum PaymentKey { Stripe, Paypal }
dependency_keyed_as!(PaymentKey, Payment: [
(PaymentKey::Stripe, singleton, StripeGateway),
(PaymentKey::Paypal, transient, PaypalGateway),
]);
```
### Important rules
1. **Single: one macro, one binding** — two `dependency!(singleton, MyService)` calls produce two conflicting `impl Resolver<MyService> for Container`.
2. **Many/keyed: one batch per macro call** — each entry declares its lifecycle explicitly: `(singleton, Impl)` or `(transient, Impl)` for many; `(key, lifecycle, Impl)` for keyed.
3. **Many / keyed are per-crate** — register the full batch in the library crate that owns the trait; the app links that crate to resolve.
4. **Keyed keys are typed** — declare a key type `K` (`enum`, `struct`, …); keys must be `PartialEq`. Lookup: `.one(key_value)`.
5. **Trait objects** — `_as` macros take the trait name only (`Logger: Impl`); they expand to `dyn Trait + Send + Sync`. Traits should extend `Send + Sync` (recommended supertraits).
---
## Resolution: `resolve()`
A single function; the **generic type** selects the mode:
```rust
use std::sync::Arc;
use noema::{resolve, Keyed};
// Single concrete → Arc<T>
let svc: Arc<MyService> = resolve::<MyService>();
// Single trait object → Arc<dyn Trait + Send + Sync>
let log = resolve::<dyn Logger + Send + Sync>();
// Many → ordered slice (order = registration order)
let validators = resolve::<Arc<[Arc<dyn Validator + Send + Sync>]>>();
assert_eq!(validators.len(), 2);
// Keyed → wrapper; .one(key) returns Option
let payments = resolve::<Keyed<PaymentKey, dyn Payment + Send + Sync>>();
let stripe = payments.one(PaymentKey::Stripe).expect("registered");
assert!(payments.one("unknown").is_none());
// Empty many → empty slice (no panic)
let empty = resolve::<Arc<[Arc<dyn Unused + Send + Sync>]>>();
assert!(empty.is_empty());
```
| `resolve::<T>()` | `Arc<T>` |
| `resolve::<dyn Trait + Send + Sync>()` | `Arc<dyn Trait + Send + Sync>` |
| `resolve::<Arc<[Arc<T>]>>()` | `Arc<[Arc<T>]>` |
| `resolve::<Keyed<K, S>>()` | `Keyed<K, S>` |
If there is no registration for a **single** type, the code does not compile (missing `impl Resolver<…> for Container`).
---
## `#[derive(Injectable)]`
Generates `impl<TInjector> Injectable<TInjector>` by resolving fields from the injector:
```rust
use noema::core::{arc_dyn, Injectable, Resolver};
#[derive(Injectable)]
struct AppService {
repo: Arc<UserRepo>, // requires TInjector: Resolver<UserRepo>
logger: arc_dyn!(Logger), // requires TInjector: Resolver<dyn Logger + Send + Sync>
validators: Arc<[Arc<dyn Validator + Send + Sync>]>, // TInjector: ManyResolver<Validator>
}
// Resolver and ManyResolver must be in scope where you use the derive:
use noema::core::Resolver;
use noema::di::ManyResolver;
let app = AppService::inject(&Container);
```
Supported fields: `Arc<T>`, `arc_dyn!(Trait)`, `Arc<[Arc<T>]>`.
For framework traits (`Mediator`, `ManyResolver`, `KeyedResolver`) the derive uses `Arc::new(Container)` — the ZST acts as the handle. Item sets are normal singletons: `Arc<ItemSet<C>>` goes through `Resolver`.
---
## Type helpers (`core`)
```rust
use noema::core::Arc;
use noema::{arc_dyn, dyn_sync};
type LoggerArc = arc_dyn!(Logger); // Arc<dyn Logger + Send + Sync>
type LoggerObj = dyn_sync!(Logger); // dyn Logger + Send + Sync
```
---
## Cross-crate registration
Register in library crates; resolve in the app crate that depends on them:
```rust
// validators crate (lib.rs)
use noema::core::{Container, Injectable};
use noema::dependency_as_many;
pub trait Validator: Send + Sync { fn id(&self) -> &'static str; }
// … EmailValidator, PhoneValidator, SmsValidator …
dependency_as_many!(Validator: [
(singleton, EmailValidator),
(singleton, PhoneValidator),
(singleton, SmsValidator),
]);
```
```rust
// binary / test (depends on validators)
use std::sync::Arc;
use noema::resolve;
use validators::Validator;
let all = resolve::<Arc<[Arc<dyn Validator + Send + Sync>]>>();
assert_eq!(all.len(), 3);
```
Register the **full batch in one crate** — duplicate `impl ManyResolver` / `KeyedResolver` for the same trait is a compile error.
---
## Minimal complete example
```rust
use std::sync::Arc;
use noema::core::{Container, Injectable, Resolver};
use noema::{resolve, dependency, dependency_as};
struct Config {
pub host: String,
}
impl Injectable<Container> for Config {
fn inject(_: &Container) -> Self {
Config { host: "localhost".into() }
}
}
trait Greeter: Send + Sync {
fn greet(&self) -> &str;
}
struct Hello;
impl Greeter for Hello {
fn greet(&self) -> &str { "hi" }
}
impl Injectable<Container> for Hello {
fn inject(_: &Container) -> Self { Hello }
}
dependency!(singleton, Config);
dependency_as!(singleton, Greeter: Hello);
fn main() {
let cfg = resolve::<Config>();
let greeter = resolve::<dyn Greeter + Send + Sync>();
assert_eq!(cfg.host, "localhost");
assert_eq!(greeter.greet(), "hi");
}
```
---
## Feature flag
```toml
[dependencies]
noema = { version = "0.3", default-features = true } # includes "di"
# or explicit:
noema = { version = "0.3", features = ["di"] }
```