What Is a Provider?
An Effect describes a computation that needs an environment. A ProviderSpec describes how to build one capability and register it in Env.
Effect<User, DbError, caps!(Database)>
└── "I need Database to produce a User"
ProviderSpec for DatabaseLive
└── "I need Config (via requires) to produce Database"
Effects declare needs; providers declare construction.
The ProviderSpec trait
#![allow(unused)] fn main() { use id_effect::{CapabilityId, Env, ProviderError, ProviderSpec}; struct DatabaseLive; impl ProviderSpec for DatabaseLive { type Key = Database; type Output = Pool; fn provider_id() -> &'static str { "database-live" } fn requires() -> &'static [CapabilityId] { // Declare Config as a dependency for graph ordering // (return a static slice of CapabilityId values) &[] } fn provide(deps: &Env) -> Result<Pool, ProviderError> { let config = deps.get::<Cap<Config>>(); Ok(connect_pool(config.db_url())) } } }
type Key— which capability this provider registerstype Output— the concrete value stored inEnvprovide(deps)— build the value; read dependencies withdeps.get::<Cap<K>>()requires()— capability ids this provider needs built first (used byCapabilityGraph)
Providers are lazy recipes
Implementing ProviderSpec does nothing by itself. Construction runs when you call run_with, build_env, or CapabilityGraph::build.
The key insight
- Effects are programs — what to do with capabilities
- Providers are constructors — how to build capabilities
provide!(DatabaseLive) registers the recipe; run_with executes the graph and passes the resulting Env to your effect.
Lifecycle and cleanup
Providers run synchronously at startup. For resources with async teardown, build handles in provide and register finalizers in effect Scope code (see Chapter 10). The provider graph itself focuses on construction order, not fiber lifetimes.