Recovery Combinators — catch, fold, and Friends
Knowing about Cause and Exit is only useful if you can act on them. id_effect provides a focused set of recovery combinators.
catch: Handle Expected Errors
catch intercepts Cause::Fail(e) and gives you a chance to recover:
#![allow(unused)] fn main() { let resilient = risky_db_call() .catch(|error: DbError| { match error { DbError::NotFound => succeed(User::anonymous()), other => fail(other), // re-raise anything else } }); }
If risky_db_call fails with Cause::Fail(e), the closure runs. If it fails with Cause::Die or Cause::Interrupt, those propagate unchanged — catch only handles typed failures.
catch_all: Handle Everything
catch_all intercepts any Cause:
#![allow(unused)] fn main() { let bulletproof = my_effect.catch_all(|cause| match cause { Cause::Fail(e) => handle_expected_error(e), Cause::Die(_) => succeed(fallback_value()), Cause::Interrupt => succeed(cancelled_gracefully()), }); }
Use catch_all when you genuinely need to handle panics or cancellation — typically at resource boundaries or top-level handlers. Don't use it to swallow defects silently.
fold: Handle Both Paths
fold transforms both success and failure into a uniform success type:
#![allow(unused)] fn main() { let always_string: Effect<String, Never, ()> = risky_call() .fold( |error| format!("Error: {error}"), |value| format!("Success: {value}"), ); }
After fold, the effect never fails (E = Never). Both arms produce the same type. This is useful for logging, metrics, or converting to a neutral representation.
or_else: Try an Alternative
or_else runs an alternative effect on failure:
#![allow(unused)] fn main() { let with_fallback = primary_source() .or_else(|_err| secondary_source()); }
If primary_source fails, secondary_source runs. If that also fails, the combined effect fails with the second error. Useful for fallback chains.
ignore_error: Discard Failures
When you genuinely don't care about an operation's success:
#![allow(unused)] fn main() { // Log "best effort" — failure is acceptable let logged_effect = log_metrics() .ignore_error() .flat_map(|_| actual_work()); }
ignore_error converts Effect<A, E, R> to Effect<Option<A>, Never, R>. The effect always "succeeds" — with Some(value) on success or None on failure.
The Recovery Hierarchy
catch(f) — handles Cause::Fail only
catch_all(f) — handles all Cause variants
fold(on_e, on_a) — transforms both paths to success
or_else(f) — runs alternative on failure
ignore_error — converts failure to Option
Prefer the narrowest combinator that solves your problem. catch for expected errors. catch_all only when you need to touch panics or cancellation.