Providing Dependencies — run_with and Env
An effect with R = caps!(…) cannot run until its capabilities exist. Provide at the edge, not inside library code.
run_with — the main entrypoint
use id_effect::{Effect, ProviderSpecDerive, caps, effect, provide, require, run_with, succeed}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Counter(pub u32); #[derive(ProviderSpecDerive)] #[provides(Counter)] struct CounterLive; impl CounterLive { fn new() -> Counter { Counter(42) } } fn app() -> Effect<u32, (), caps!(Counter)> { effect!(|r| { let counter = ~Counter; counter.0 }) } fn main() { let n = run_with([provide!(CounterLive)], app()).expect("run"); }
provide! wraps a ProviderSpec as a ProviderBox. run_with collects providers, plans build order via CapabilityGraph, and runs the effect.
Multiple providers
Pass every provider the app needs in one list — order in the array does not matter; the graph topologically sorts by requires():
#![allow(unused)] fn main() { run_with( [ provide!(ConfigLive), provide!(DatabaseLive), provide!(LoggerLive), provide!(UserRepoLive), ], my_application(), )?; }
Manual Env for tests
When you only need a handful of values, build Env by hand:
#![allow(unused)] fn main() { let mut env = Env::new(); env.insert::<Cap<Database>>(mock_pool); env.insert::<Cap<EffectLogger>>(test_logger); let user = run_blocking(get_user(42), env)?; }
build_env is the middle ground — same provider types as production, but you get the Env back without running an effect:
#![allow(unused)] fn main() { let env = build_env([provide!(MockUserRepoLive)])?; run_test(get_user(1), env)?; }
Where to provide
Provide at main, test setup, or HTTP/Tokio boundaries — not inside library functions.
#![allow(unused)] fn main() { // BAD — library reaches for concrete deps pub fn process_order(order: Order) -> Effect<Receipt, AppError, ()> { let db = connect("hardcoded-url"); // ... } // GOOD — library declares needs; caller wires them pub fn process_order(order: Order) -> Effect<Receipt, AppError, caps!(Database, EffectLogger)> { // ... } }
Summary
| API | Purpose |
|---|---|
run_with([provide!(P), …], effect) | Build graph + run |
build_env([provide!(P), …]) | Build Env only |
Env::insert::<Cap<K>>(value) | Manual test wiring |
run_blocking(effect, env) | Run when Env is already built |
None of these execute effect steps until run_with / run_blocking / run_async is called — wiring stays lazy until the boundary.