Exit — Terminal Outcomes
Every effect execution ends with an Exit. It's the final word on what happened.
The Exit Type
#![allow(unused)] fn main() { use id_effect::Exit; enum Exit<A, E> { Success(A), // Effect completed, produced A Failure(Cause<E>), // Effect failed with Cause<E> } }
Exit combines the success type and the full failure taxonomy.
run_blockingreturnsResult<A, E>— you only see typedEfailures; defects and interrupts are not represented as values there.run_testreturnsExit<A, E>and is the right harness for unit tests that must assert on defects, interrupts, and typed failures.
For CLI / process exit codes, map an Exit with id_effect_cli::exit_code_for_exit (see CLI with clap).
Converting Exit to Result
Most application code wants Result. The conversion is straightforward:
#![allow(unused)] fn main() { let result: Result<User, AppError> = exit.into_result(|cause| match cause { Cause::Fail(e) => AppError::Expected(e), Cause::Die(_) => AppError::Defect, Cause::Interrupt(_) => AppError::Cancelled, }); }
Or use the convenience method that maps Cause::Fail(e) → Err(e) and treats other causes as panics:
#![allow(unused)] fn main() { let result: Result<User, DbError> = exit.into_result_or_panic(); }
Exit in Fiber Joins
When you join a fiber (Chapter 9), you get an Exit back:
#![allow(unused)] fn main() { let fiber = my_effect.fork(); let exit: Exit<A, E> = fiber.join().await; }
This lets you inspect whether the fiber succeeded, failed with a typed error, panicked, or was cancelled — and respond appropriately in the parent fiber.
Practical Rule
Use run_blocking (which returns Result<A, E>) for most application logic. Use run_test when you need the full Exit taxonomy in tests. At the process edge (binaries), combine run_main / exit_code_for_exit from id_effect_cli with the table in the CLI exit codes chapter.