# Handlers

URL: /docs/tour/handlers
Markdown: /docs/tour/handlers.md

> How handlers give effects their meaning, how to install them with `with`, handler state, and how tests swap handlers.

> **Pre-alpha**
>
> The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates.

An [effect](/docs/tour/effects) declares operations. A **handler** gives them meaning. Code names the effect; the caller picks the handler. The same function can run against SQLite in production and an in-memory table in a test.

## Writing a handler [#writing-a-handler]

`handler Eff { … }` is an expression. It produces a value of type `Handler<Eff, r>`, where `r` is the row its clauses perform. `Handler<Eff>` is short for `Handler<Eff, {}>`.

```sakura
effect Counter {
  fn next() -> Int
  fn reset() -> Unit
}

fn memory_counter(start: Int) -> Handler<Counter> {
  handler Counter {
    var n: Int = start
    fn next() { n = n + 1; n }
    fn reset() { n = start }
  }
}
```

- There is exactly one clause per operation (`V3011`), with the same number of parameters.
- Clause parameters are not annotated. Their types come from the `effect` declaration.
- `var` members are **handler state**. They are visible only inside the clauses, initialized once per installation, and updated by assignment. The runtime runs clauses of one instance one at a time.

## Installing handlers with `with` [#installing-handlers-with-with]

`with h1, h2 { body }` runs `body` with the handlers installed. Handlers are installed innermost-last: `h2` sits inside `h1`. The `with` expression's row is the body's row minus each handled effect, plus each handler's `r`.

```sakura
fn label_all(items: List<String>) -> List<String> ! {Counter} {
  List.map(items, fn(item) { "{Counter.next()}. {item}" })
}

test "labels count from the handler's start" {
  let out = with memory_counter(0) { label_all(["a", "b"]) }
  expect_eq(out, ["1. a", "2. b"])
}
```

`label_all` requires `Counter`. The `with` satisfies it, so the test body has no `Counter` left in its row. Using `with` on a value that is not a handler is `V3012`.

## Resumption without `resume` [#resumption-without-resume]

A clause runs in place of the operation call. Its return value is the operation's result. This happens exactly once, in tail position, by construction. Handlers are **tail-resumptive**: there is no `resume`, no multi-shot resumption, and no first-class continuations. `resume` is a reserved keyword (`V0007`).

This keeps handlers cheap and predictable. A handler is a service the body calls, not a construct that can rewind or duplicate the body. Generators and backtracking would need non-tail `control` operations; design §16 lists them as a possible later edition, not v0.

## Nesting and shadowing [#nesting-and-shadowing]

For the same effect, the innermost handler wins. There is no masking or injection.

```sakura
test "inner handler shadows outer" {
  let out = with memory_counter(0) {
    with memory_counter(100) { Counter.next() }
  }
  expect_eq(out, 101)
}
```

## Clauses that perform effects [#clauses-that-perform-effects]

A clause may perform other effects. They go to the handlers installed **outside** the `with`. Calling the same effect inside its own clause reaches the next outer handler, which is how wrappers work:

```sakura
fn prefixed(prefix: String) -> Handler<Log, {Log}> {
  handler Log {
    fn event(level, message, fields) { Log.event(level, "{prefix}{message}", fields) }
  }
}
```

`prefixed` handles `Log` and performs `Log` on the outer handler, so its type is `Handler<Log, {Log}>`. A `Db` handler whose clauses call `Http` has type `Handler<Db, {Http}>`, and installing it adds `Http` to the surrounding row. A handler cannot create authority from nothing.

A clause may `fail` only with tags declared on its operation. Given this declaration:

```sakura
error DbUnavailable(reason: String)

effect Db {
  fn exists(table: Table, key: String) -> Bool
  fn insert(table: Table, row: Json) -> Unit ! {Fail<DbUnavailable>}
}

fn memory_db() -> Handler<Db> {
  handler Db {
    var rows: Map<String, Json> = Map.empty()
    fn exists(table, key) { Map.contains(rows, "{table}/{key}") }
    fn insert(table, row) {
      rows = Map.insert(rows, "{table}/{Json.get_string(row, "id")}", row)
    }
  }
}
```

the `insert` clause may `fail DbUnavailable { … }`, and `exists` may not fail at all.

## Failure is not handled with handlers [#failure-is-not-handled-with-handlers]

`Fail<E>` is the one compiler-known effect. You recover from it with `try`/`catch`, not with a handler. See [Failure](/docs/tour/effects#failure-faile-and-error-unions).

## Handlers are values [#handlers-are-values]

Handlers are first-class. A function can return one, and a record can store one. A handler that captured a capability is a scoped value: it cannot outlive the `with` that produced the capability (`V6002`). When a `with` exits, by normal return, `fail`, defect, or cancellation, its handler state and captured resources are released.

## Root handlers come from the host [#root-handlers-come-from-the-host]

`main` declares the std effects the program needs. The host supplies root handlers for exactly those, limited by `[permissions]` in `sakura.toml`. Only std effects may appear in `main`'s row (`V6010`); user effects such as `Counter` must be handled inside the program. This program reads its arguments, so it declares `Cli`. From design §15.6 (abridged):

```sakura
import std/cli.{Cli, CliError}
import std/fs.{Fs, FsError, Dir}

pub fn main() -> Unit ! {Console, Cli, Fs, Fail<CliError | FsError>} {
  let args: Args = Cli.parse(Cli.args())
  // Narrow the root Fs handler to the data directory before touching files.
  with Fs.rooted(Dir.subdir(Fs.root(), "data")) {
    let text = Fs.read_text(args.input).trust(@trust("word counting cannot be influenced by content"))
    let top = List.slice(List.sort_by(Map.entries(word_counts(text)), fn(e) { 0 - e.1 }), 0, args.top)
    for (word, n) in top { Console.print("{word}: {n}") }
  }
}
```

Effects are requirements; handlers are authority. See [Capabilities and security](/docs/tour/capabilities).

## Standard handlers [#standard-handlers]

From design §10.2. Each constructor is a function of the effect's home module, so `Console.host()` is `std/console.host`.

| Effect | Handlers |
| --- | --- |
| `Console` | `Console.host()`, `Console.captured()` |
| `Clock` | `Clock.host()`, `Clock.fixed(instant)`, `Clock.virtual()` |
| `Random` | `Random.host()`, `Random.seeded(seed)` |
| `Env` | `Env.host(scope)`, `Env.of(map)`, `Env.only(keys)` |
| `Log` | `Log.stderr()`, `Log.json()`, `Log.captured()` |
| `Fs` | `Fs.host(dir)`, `Fs.rooted(dir)`, `Fs.memory(files)` |
| `Http` | `Http.host(scope)`, `Http.scoped(scope)`, `Http.scripted(script)`, `Http.recording(path)` |
| `Serve` | `Serve.host(scope)` |
| `Sql` | `Sql.sqlite(path)`, `Sql.memory()`, `Sql.postgres(url)` (v1) |
| `Cli` | `Cli.host()`, `Cli.of(args)` |
| `Budget` | `Budget.limits(limits)`, `Budget.unlimited()` |
| `Llm` | `Llm.anthropic(key, model)`, `Llm.scripted(responses)`, `Llm.replay(path)` |
| `Task` | `Task.host()`, `Task.deterministic(seed)` |

## Tests swap handlers [#tests-swap-handlers]

A `test` runs under deterministic default handlers: `Clock.fixed(Instant.from_unix_millis(0))`, `Random.seeded(seed)` (from `sakura test --seed`), `Env.of(Map.empty())`, `Console.captured()`, and `Log.captured()`. Captured output can be checked with `expect_output(lines)`. Any other effect must be handled inside the test. Otherwise `sakura check` reports `V3020` ("test uses Db; install a handler with `with`"). Typed failures are the exception: by design a test body may let `Fail<E>` escape, and the test then fails with the error's `Show`. The current compiler does not implement that yet and still reports `V3020` for it.

```sakura
test "deterministic under virtual time" {
  with Http.scripted([(Any, Response.status(200, "x"))]), Clock.virtual(), Random.seeded(1), Task.deterministic(1) {
    expect_eq(fetch_both(Url.from("https://a"), Url.from("https://b")), ("x", "x"))
  }
}
```

`fetch_both` is the example on the [Concurrency](/docs/tour/concurrency#timeouts-retries-and-budgets) page. Because time, randomness, scheduling, and the network are all handlers, a test controls every one of them.

## Retry, timeout, budget [#retry-timeout-budget]

`retry` and `timeout` are ordinary std functions over thunks, not handlers. `Budget` is an effect, and `Budget.limits(limits)` is its handler, configured with a `Limits { tokens, usd, calls }` record. See [Concurrency](/docs/tour/concurrency#timeouts-retries-and-budgets).

## What the compiler checks [#what-the-compiler-checks]

- A `handler` has exactly one clause per operation, with matching parameter counts (`V3011`).
- `with` is applied only to handler values (`V3012`).
- A `with` removes the effects it handles and adds each handler's row. The result is checked against the signature (`V3001`, reported at the clause when a clause performs an undeclared effect).
- A clause fails only with tags declared on its operation.
- A test installs a handler for every effect outside the deterministic defaults (`V3020`).
- A handler that captured a capability does not escape its scope (`V6002`).
- `main`'s row contains only std effects (`V6010`).

## Next [#next]

[Contracts](/docs/tour/contracts): refinement types, `requires`, and `ensures`.
