Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

API Tiers and Composition Decisions

Stillwater keeps a broad API, but not every module belongs in the default mental model. Use the smallest tier that solves the application problem.

Core

Start here for most applications:

  • Validation and NonEmptyVec for independent checks and accumulated errors.
  • Effect, free constructors, and essential combinators for imperative boundaries.
  • Reader helpers (ask, asks, local) for explicit dependencies.
  • ContextError for adding boundary context.
  • .boxed() and BoxedEffect only where type erasure is required.

The recommended architecture is data first: load facts in the shell, pass them to a pure function that returns a decision or plan, then interpret that plan at the boundary. See the user_registration example for the canonical shape. Facts are snapshots: the repository still enforces uniqueness atomically when committing a plan and reports conflicts separately.

Operational

Reach for these when runtime behavior is part of the requirement:

  • Explicit sequential or parallel traversal and sequencing.
  • Parallel execution, races, concurrency limits, retry, and timeout.
  • Runtime bracket helpers for resources whose cleanup is itself effectful.
  • tracing integration for production diagnostics.

The Cargo feature named async enables Tokio-backed retry and timeout helpers. Core Effect composition is always asynchronous and does not require that feature.

Advanced

These modules remain supported, but should be adopted for a specific need rather than as the default application architecture:

  • IO is a narrow, infallible projection helper. Prefer ordinary Effect constructors for fallible service calls and do not treat IO as a second general effect system.
  • WriterEffect accumulates typed output as part of a result. Use it when the accumulated value is domain data; use tracing for operational logs and spans.
  • SinkEffect streams emissions to a supplied consumer. Use it when values must be handled incrementally instead of retained in memory.
  • Type-level resource tracking proves acquire/release accounting in composition. Prefer ordinary Rust RAII for ownership-bound cleanup and runtime bracket when cleanup is asynchronous. Use bracket_full when cleanup errors must be represented in the result. Async brackets do not mask cancellation; see the migration guide.

Decision table

NeedDefault choice
Accumulate independent validation errorsValidation
Describe an I/O boundaryEffect plus a narrow environment trait
Operational logs and spanstracing
Typed audit output returned with a valueWriterEffect
Incremental emissionsSinkEffect
Ownership-bound cleanupRAII (Drop)
Async/fallible cleanup protocolRuntime bracket
Compile-time acquire/release accountingType-level resource API
Sync environment accessfrom_fn
Async work owning cheap cloned handlesfrom_async
Async work borrowing the environmentfrom_async_ref

These tiers classify the existing surface for 2.0. They do not introduce feature gates or move APIs to companion crates.