Providing Dependencies — The provide Method
An effect with a non-() R can't be run directly. To run it, you must satisfy its requirements. The primary tool is .provide().
Basic provide
#![allow(unused)] fn main() { fn get_user(id: u64) -> Effect<User, DbError, Database> { ... } let effect: Effect<User, DbError, Database> = get_user(42); // Satisfy the Database requirement let ready: Effect<User, DbError, ()> = effect.provide(my_database); // R is now () — we can run it let user = run_blocking(ready)?; }
.provide(value) takes a value of whatever type R needs and returns a new effect where that requirement is satisfied. When R becomes (), the effect is runnable.
Providing Multiple Dependencies
If R = (Database, Logger), call .provide() twice:
#![allow(unused)] fn main() { fn logged_get_user(id: u64) -> Effect<User, AppError, (Database, Logger)> { ... } let user = run_blocking( logged_get_user(42) .provide(my_database) .provide(my_logger) )?; }
Order doesn't matter — each .provide() removes one requirement from the tuple. After both, R = ().
Partial Providing with provide_some
Sometimes you want to satisfy some requirements now and the rest later:
#![allow(unused)] fn main() { // Provides Database, still needs Logger let partial: Effect<User, AppError, Logger> = logged_get_user(42).provide_some(my_database); // Later, provide the Logger let ready: Effect<User, AppError, ()> = partial.provide(my_logger); }
provide_some is useful when you're building up an effect in layers — each layer provides what it knows about.
Providing Layers (Preview)
For most real applications, you don't call .provide() with raw values. Instead, you use Layers — recipes that know how to construct dependencies from other dependencies:
#![allow(unused)] fn main() { // The idiomatic way in real apps let app_effect = my_business_logic() .provide_layer(app_layer); }
Layers are covered in Chapter 6. For now, think of .provide(value) as the low-level primitive and Layers as the high-level pattern built on top.
Where to Call provide
The rule is simple: provide at the program edge, not inside library functions.
Library functions should stay generic over R:
#![allow(unused)] fn main() { // BAD — library function reaches in and provides its own deps pub fn process_order(order: Order) -> Effect<Receipt, AppError, ()> { let db = Database::connect("hardcoded-url"); // where did this come from? inner_process(order).provide(db) } // GOOD — library returns requirements in R, caller provides pub fn process_order(order: Order) -> Effect<Receipt, AppError, Database> { inner_process(order) } }
The caller — main, a test, or a higher-level orchestrator — knows what database to use. The library function should not.
Summary
#![allow(unused)] fn main() { // Satisfy one requirement effect.provide(value) // Satisfy one of several requirements effect.provide_some(value) // Satisfy with a Layer (see Ch6) effect.provide_layer(layer) }
All three return a new effect with a smaller (or empty) R. None of them execute anything — .provide() is still lazy.