---
title: "Effect boundaries"
description: "The capability probe, the transport contract, and the safety rules the compiler enforces around Effect code."
image: "https://hem.run/social/hem.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://hem.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Effect boundaries

The Effect extension activates only when `hem.effectImportSource` is configured. When this option is omitted, these rules do not apply and the compiler emits no Effect imports.

The preview targets the native `effect` package at `effect@4.0.0`,
configured as `"effectImportSource": "effect"`. The capability probe below does
not certify other versions. See [Effect setup](/guides/enable-effect).

## The capability probe

At build start, the compiler inspects the configured Effect module to verify that it exports the required members.

| Namespace | Required members |
| --- | --- |
| `Effect` | `gen`, `fail`, `succeed`, `suspend`, `all`, `scoped`, `tryPromise`, `fromOption`, `fromResult`, `catchTags`, `catch`, `fn`, `fnUntraced`, `addFinalizer` |
| `Layer` | `effect`, `provide`, `mergeAll` |
| `Data` | `TaggedError` |
| `Context` | `Service` |
| `Cause` | `UnknownError`, `NoSuchElementError` |
| `Option` | `some`, `none` |
| `Result` | `succeed`, `fail` |

The compiler also verifies that `Effect.fromOption` and `Effect.fromResult` lift the module's own `Option` and `Result` inside `Effect.gen`; these are the lifts postfix `!` emits for Effect carriers.

If any capability is missing, the compiler reports error HEM3001 and halts the build. Effect types and functions are identified by their module declaration origin rather than their name, preventing conflicts with user-defined types named `Effect`.

A public conformance suite verifies the probe with runtime fixtures covering finalizer order, partial grouped acquisition, release on interruption, release before `catch`, boxed `Option` and `Result` lifts, and Layer wiring.

## Canonical carrier lifts

Standard eager carrier types belong to Hem's zero-dependency standard library rather than Effect. Inside an `effect` block, postfix `!` adapts them as follows:

| Input | Success | Failure |
| --- | --- | --- |
| canonical `Result<A, E>!` | `A` | the original `E` |
| canonical `Option<A>!` | `A` | checked `NoSuchElementError` |
| native `?A!` | refused (HEM2027) | no error policy exists to invent |

The lift is an `Effect.suspend` that reads the carrier once and returns `Effect.fail` for the residual or `Effect.succeed` for the payload; no Hem helper is emitted.

When pattern-matching with `let Ok` or `let Some` without an `else` branch inside an `effect` block, unmatched values are returned on the success channel, widening the success type rather than failing the effect. Use `result!` or `option!` to lift errors into the Effect failure channel, or use an explicit `else raise` when missing values should become domain errors. Resource cleanup, interruption, error handling, and channel provenance operate normally across adapted values.

Effect's own eager carriers are `Option<A>` and `Result<A, E>`. They are not yieldable inside `Effect.gen` in this cohort, so postfix `!` on a checked Effect `Option` or `Result` emits `Effect.fromOption` or `Effect.fromResult`. The bare name `Result` in a `.hem` file always refers to the canonical type imported from `@hem-lang/std/result`; Effect's is written `Result.Result<A, E>`. Contextual `Ok` and `Err` constructors and patterns remain valid on checked Effect `Result` values and follow its `Success`/`Failure` representation.

## Provenance

The compiler tracks the success, error, and requirement channels at each `!`, `raise`, and `await` expression. This tracking enables diagnostics to pinpoint the exact line that introduced an error or dependency, rather than only reporting a mismatch at the top-level call site.

For example, if a `Users` service is missing at `Effect.runPromise`, the error message links directly to the `Users!` call that required it. Similarly, unhandled errors link back to the `raise` statement or operation that added the error type.

## Bind and block rules

| Code | Rule |
| --- | --- |
| HEM2004 | `!` appears outside an effect block |
| HEM4103 | `!` crosses an arrow or function boundary; the fix names `Effect.forEach` |
| HEM2009 | A nested bind inside a group member, which would silently serialize |
| HEM4105 | A `Scope`-carrying effect is bound with `!` instead of `use`; the fix rewrites `let lease = acquire()!` to `use lease = acquire()`, keeping the authored name |
| HEM4114 | A traced operation is bound somewhere without stable trace identity |

`effect traced` requires one named, immutable binding with a block body. Local bindings may capture dependencies. Names include the module and enclosing named bindings; collisions receive a source-location suffix. Executions of one definition share a name. HEM4114 still rejects mutable, grouped, destructured, wildcard, or anonymous definitions. Each invocation owns its cleanup; capturing a service does not extend its lifetime or let ordinary callbacks borrow the operation's bind boundary.

## Execution and wiring rules

| Code | Rule |
| --- | --- |
| HEM4102 | Requirements were never provided; blame points at the sites that introduced them |
| HEM4106 | An `Effect.run*` call appears inside an Effect rather than at an application edge |
| HEM4107 | More than one provide boundary in a single lifecycle |
| HEM4108 | A `Layer.mergeAll` whose members depend on one another |
| HEM4109 | A service implementation leaks its own construction requirements |

These rules enforce consistent runtime lifecycles. Calling an `Effect.run*` runner inside an effect drops the parent supervision, contextual services, test clock, interruption scope, and tracing context. Multiple provide boundaries can cause layers to be constructed multiple times. Merging interdependent layers with `Layer.mergeAll` loses dependency ordering. Hem flags these patterns at compile time while allowing explicit Effect library calls when separate lifecycles are required.

## Channel safety rules

| Code | Rule |
| --- | --- |
| HEM4110 | An exported Effect boundary has an `any` or `unknown` error or requirement channel |
| HEM4111 | An assertion narrows away success, failure, or service members |
| HEM4115 | A `Promise` or `PromiseLike` is hidden in the success channel of `Effect.succeed`, `Effect.sync`, or another Effect-returning call |
| HEM4118 | An Effect is nested in a generator return, a success channel, or a failure channel |
| HEM4119 | `for await` appears inside an Effect block, where `await` lifts a single Promise |

When Hem's only errors are `any`/`unknown` consequences, TypeScript errors are still reported first; HEM4110 is withheld when a TypeScript error lies inside its boundary span in the same file.

Assertions between Effect types may widen channels, but cannot narrow them
(HEM4111). An `unknown` value cannot be asserted to a concrete Effect type,
including at an import boundary or after passing through an alias or helper
(HEM2028). Use a correctly typed library contract or construct an Effect from
checked data. An unchecked local TypeScript shim requires a separate boundary
review; it is not proof supplied by Hem.

The same rule applies through unknown-returning helpers, generic identity
helpers, and capturing closures. Authored predicate and assertion signatures
cannot supply the missing evidence (HEM2029); imported signatures remain trusted
foreign contracts. A checked widening preserves the original Effect object.
An empty typed-error channel does not rule out defects or interruption.

Transfers between configured Effect types also check write access in their
success, error, and requirement values (HEM2030). A wider Effect view cannot make
a readonly payload writable. Effect's internal variance markers remain part of
the trusted library contract, not application records checked by Hem.

HEM4115 prevents wrapping a `Promise` in `Effect.succeed` or `Effect.sync`, where Effect cannot manage its cancellation, retries, or errors. Asynchronous operations must enter through Hem's `await` inside an `effect` block or through `Effect.tryPromise`.

HEM4118 prevents returning an unexecuted `Effect` value inside another `Effect`'s success channel. Use postfix `!` to execute and bind nested effects.

## Resource rules

| Code | Rule |
| --- | --- |
| HEM4112 | `defer` outside an Effect scope |
| HEM4113 | A cleanup value whose type does not satisfy the contract |

`defer` accepts an `Effect<void, never, R>`. Synchronous cleanup functions must be lifted explicitly, return values must be discarded, and errors must be handled before registration. Exit-sensitive cleanups use `Effect.addFinalizer`.

Finalizers run on success, failure, defect, and interruption in last-in, first-out (LIFO) order. Scopes close before any outer `catch` block runs, ensuring resources are released before recovery logic executes.

## Installation identity rules

| Code | Rule |
| --- | --- |
| HEM4116 | Resolved declarations come from more than one physical `effect` installation |
| HEM4117 | A generic class directly or transitively inherits the configured `Context.Service` |

HEM4116 triggers when multiple physical installations of `effect` are found, even if their version numbers match. The compiler compares canonical filesystem paths, so symlinks to the same installation are treated as one.

HEM4117 prohibits generic service classes because TypeScript type arguments are erased at runtime; for example, `Cache<User>` and `Cache<Order>` would share the same runtime tag key. Generic methods on non-generic services remain supported.

## What remains ordinary Effect

Hem provides dedicated syntax for common control flow patterns, while runtime policies remain explicit Effect API calls. Scheduling and retries, collection concurrency, fibers, streams, layer configuration, exit-sensitive finalizers, and cancellation adapters use library functions. Follow the API contract of the pinned runtime: documentation for another Effect version may use different names or signatures. Effect tooling must also be compatible with that version and the generated code; the compiler probe does not certify third-party tools.

## Normative source

Specification 0.2, §14 (the Effect extension) and §10 (canonical carriers). For the rationale behind capability probing, see [why Effect, and why Effect in Hem](/explanation/why-effect-why-hem).

Source: https://hem.run/reference/effect-boundaries/index.mdx
