> ## Documentation Index
> Fetch the complete documentation index at: https://forst-lang.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Ensure and narrowing

> Explicit validation and type refinement with ensure and is.

Use **`ensure`** with **`is`** to validate at runtime and narrow types. Forst has no exceptions. Coercion is never silent.

## Basic ensure

Reject invalid input at runtime and attach a domain error with **`or`**:

<Tabs>
  <Tab title="Forst">
    ```ft theme={"languages":{"custom":["/languages/forst.json"]}}
    func mustNotExceedSpeedLimit(speed Int) {
    	ensure speed is LessThan(100)
    		or TooFast("Speed must not exceed 100 km/h")
    }
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"languages":{"custom":["/languages/forst.json"]}}
    // forst build
    func mustNotExceedSpeedLimit(speed int) error {
    	if !(speed < 100) {
    		return TooFast{Message: "Speed must not exceed 100 km/h"}
    	}
    	return nil
    }
    ```
  </Tab>
</Tabs>

* **`ensure x is Condition`**: runtime check; failure returns or propagates an error depending on context
* **`or …`**: attach a domain-specific failure when the condition does not hold

The compiler understands the **`is`** operator for built-in constraints (`LessThan`, `Min`, `Present`, `Ok`, …).

## Nil and presence

Forst uses **`Nil`** for absence checks today. Full optional **value types** (`T | Nil`) are still [planned](/docs/resources/roadmap); what works now is **`nil`** on nilable types (pointers, `Map`, `Array`, `Error`), **`ensure … is Nil()`**, and **`*T`** [`.Present()` / `.Nil()`](/docs/language/shapes-and-constraints) on pointer fields.

<Tabs>
  <Tab title="Forst">
    ```ft theme={"languages":{"custom":["/languages/forst.json"]}}
    ensure err is Nil()
    	or err
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"languages":{"custom":["/languages/forst.json"]}}
    // forst build
    if err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

Negated forms work for error checks:

<Tabs>
  <Tab title="Forst">
    ```ft theme={"languages":{"custom":["/languages/forst.json"]}}
    ensure !err
    	or err
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"languages":{"custom":["/languages/forst.json"]}}
    // forst build
    if err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

## Result and Ok narrowing

Map lookups and other partial operations yield **`Result(V, Error)`** instead of comma-ok tuples:

<Tabs>
  <Tab title="Forst">
    ```ft theme={"languages":{"custom":["/languages/forst.json"]}}
    avail := catalog[in.stockKeepingUnit]
    ensure avail is Ok()
    ensure in.quantity is Max(avail) or InsufficientStock({
    	stockKeepingUnit: in.stockKeepingUnit,
    	requested:        in.quantity,
    	available:        avail,
    })
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"languages":{"custom":["/languages/forst.json"]}}
    // forst build
    avail, ok := catalog[in.stockKeepingUnit]
    if !ok {
    	return /* missing key error */
    }
    if in.quantity > avail {
    	return InsufficientStock{
    		stockKeepingUnit: in.stockKeepingUnit,
    		requested:        in.quantity,
    		available:        avail,
    	}
    }
    ```
  </Tab>
</Tabs>

**Comma-ok assignment (`v, ok := m[k]`) is not Forst syntax.** Handle failure explicitly with **`ensure avail is Ok()`** before using the success value.

At call sites, narrow success payloads:

<Tabs>
  <Tab title="Forst">
    ```ft theme={"languages":{"custom":["/languages/forst.json"]}}
    x := placeOrder({
    	stockKeepingUnit: "ITEM-1",
    	quantity:         2,
    })
    ensure x is Ok() or x
    // x is narrowed to the success type here
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"languages":{"custom":["/languages/forst.json"]}}
    // forst build
    x, err := placeOrder(PlaceOrderInput{
    	stockKeepingUnit: "ITEM-1",
    	quantity:         2,
    })
    if err != nil {
    	return err
    }
    // x is the success value here
    _ = x
    ```
  </Tab>
</Tabs>

## If-branch narrowing

`if x is …` can refine a variable's type inside the branch. That helps when you need conditional logic instead of early return:

<Tabs>
  <Tab title="Forst">
    ```ft theme={"languages":{"custom":["/languages/forst.json"]}}
    if x is Ok() {
    	println(x) // x narrowed inside this branch
    }
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"languages":{"custom":["/languages/forst.json"]}}
    // forst build — Result lowers to (T, error)
    x, xErr := one()
    if xErr == nil {
    	println(x)
    }
    ```
  </Tab>
</Tabs>

## Compared to Go and TypeScript

| Approach                   | Drawback                              |
| -------------------------- | ------------------------------------- |
| Go type assertions `x.(T)` | Can panic; no compile-time narrowing  |
| TS type assertions `as T`  | Bypasses checking; unsafe             |
| Exceptions                 | Non-local control flow; hard to trace |

Forst requires **`ensure`** with English keywords so validation stays visible in the source.

## Caveats

**`ensure`** and narrowing are **experimental**. Behavior and diagnostics are still maturing. See the [roadmap](/docs/resources/roadmap).

### Checks run once

A successful **`ensure`** or **`if`** check narrows the type for following lines. The compiler does **not** watch the value for later changes. Reassignment or field mutation can leave runtime state out of sync with the narrowed type.

See [Type guards § Checks run once](/docs/language/type-guards#checks-run-once) for a concrete example with guards.

<Warning>
  Narrowing reflects a single check at compile time. It does not lock the value at runtime.
</Warning>

### Simple names

Successor narrowing after **`ensure`** works best on **simple identifiers** (`ensure x is …`, then use `x`).

Compound paths like **`ensure req.state is …`** have partial support. Hover may show the predicate, but the typechecker does not fully re-register a refined type on the path.

### After `if` chains

Narrowing inside an **`if`** branch does not always carry past the end of the chain. The continuation may see the pre-**`if`** type until union types exist. Branch merge after **`ensure`** successors is also still expanding.

### No comma-ok

**`v, ok := m[k]`** is not Forst syntax. Map lookups and similar partial operations return **`Result(V, Error)`**. Use **`ensure avail is Ok()`** before using the success value.

### Optionals still partial

Full optional value types (**`T | Nil`**) are [planned](/docs/resources/roadmap). Today use **`nil`** on nilable types, **`ensure … is Nil()`**, and **`*T`** [`.Present()` / `.Nil()`](/docs/language/shapes-and-constraints) on pointer fields.

## What gets emitted

Ensure statements lower to Go error returns and conditionals. Failures are structured values returned through Go's error path. Custom failures use nominal error types when you provide them with **`or`**.

## Try it

From the Forst repository:

```bash theme={"languages":{"custom":["/languages/forst.json"]}}
task example:ensure
task example:result-ensure
task example:result-if
```

## Related

<CardGroup cols={2}>
  <Card title="Errors and Result" icon="triangle-exclamation" href="/docs/language/errors-and-result">
    Nominal error types and structured failures.
  </Card>

  <Card title="Type guards" icon="code-branch" href="/docs/language/type-guards">
    User-defined predicates for domain rules.
  </Card>
</CardGroup>
