The R Parameter — Your Dependencies, Encoded in Types
Chapter 1 introduced R as "what an effect needs to run." We kept it vague on purpose — you needed to understand effects before worrying about their environment.
For effects that use dependencies, R is written with caps!: a compile-time list of capability services. Pure effects use R = ().
What R means
#![allow(unused)] fn main() { use id_effect::{Effect, caps, effect, require}; fn get_user(id: u64) -> Effect<User, DbError, caps!(Database)> { effect!(|r| { let db = ~Database; Ok(db.fetch_user(id)) }) } }
Implicit |r|
When the enclosing function already returns Effect<_, _, caps!(…)> , you can write effect!(|r| { … }) and omit the environment type on r. Rust infers &mut caps!(…) from the return type. Use an explicit |r: &mut caps!(…)| when you want the macro to validate that body keys (~Key, require!(Key)) match that list.
Library code can stay generic over any R that exposes the same keys:
#![allow(unused)] fn main() { fn get_user<R>(id: u64) -> Effect<User, DbError, R> where R: id_effect::Needs<Database> + 'static, { effect!(|r: &mut R| { let db = ~Database; Ok(db.fetch_user(id)) }) } }
R is a promise to the compiler: this effect may only run where Database is available. caps! documents which keys the effect touches; at runtime run_with builds an Env that satisfies them.
How requirements are satisfied
Library code does not wire dependencies. Provide at the program edge with run_with:
#![allow(unused)] fn main() { use id_effect::{provide, run_with}; run_with( [provide!(DatabaseLive), provide!(LoggerLive)], get_user(42), )?; }
run_with builds a CapabilityGraph, constructs an Env, and runs the effect with run_blocking.
For tests you can skip the graph and build Env directly:
#![allow(unused)] fn main() { use id_effect::{Env, caps, run_blocking}; let mut env = Env::new(); env.insert::<Cap<Database>>(mock_db); run_blocking(get_user(42), caps!(Database)::from_env(env))?; }
Or use build_env when you still want provider types but not a full app run.
The next sections cover how R flows through composition, how to wire dependencies at the edge, and how capability services replace positional tuples.