---
title: "Option and Result, the boxed ones"
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.

# Option and Result, the boxed ones

`?T` remains the allocation-free choice for everyday absence. When nesting, combinators, a stable
serialized tag, or a visible recoverable failure matters, import Hem's canonical boxed carriers:

```hem
import { Option } from "@hem-lang/std/option"
import { Result } from "@hem-lang/std/result"

let port: Option<number> = Some(8080)
let parsed: Result<number, string> = Ok(8080)
```

The checked type gives `Some`/`None` and `Ok`/`Err` their representation. Without that context,
write `Option.some(8080)` or `Result.ok(8080)` so inference starts from `Option<number>` or
`Result<number, never>`. Both values are transparent readonly objects with `_tag` and `.value`, and
both are must-use even in a project that never enables Effect. `let _ = operation()` is the one
deliberate discard.

The APIs are synchronous, fixed-arity, and data-first. That makes direct and piped code one call:

```hem
let normalized = port |> Option.map((value) => value + 1)
let recovered = parsed |> Result.getOr(3000)
```

The most important operation is syntax rather than a generator helper. An omitted-`else` success
binding returns the original residual from the nearest arrow:

```hem
let decodePort = (text: string): Result<number, ParseError> => {
  let Ok(value) = parsePort(text)
  Result.ok(value)
}

let loadPort = async (): Promise<Result<number, LoadError | ParseError>> => {
  let Ok(text) = await readPortText()
  let Ok(port) = parsePort(text)
  Result.ok(port)
}
```

The RHS runs once. `Err` is returned unchanged; `Some` similarly propagates boxed `None`, while a
native `?T` binding preserves the exact observed `null` or `undefined`. The destination must use
the same representation, callbacks form their own boundary, and Promise rejection stays rejection.
Adding `else` always means ordinary local recovery. Inside `effect {}` the block itself is the
boundary: the residual returns unchanged on the Effect success channel, and only postfix `!` moves
a failure into the Effect failure channel.

Carrier bindings also group with `and`. Every member evaluates to completion — sequentially when
pure, under one `Promise.all` when awaited, under one `Effect.all` when bang-marked — and then the
members are checked in the order you wrote them; the first residual returns unchanged:

```hem
let loadPair = async (): Promise<Result<Pair, LoadError>> => {
  let Ok(left) = await readLeft() and Ok(right) = await readRight()
  Result.ok(Pair(left, right))
}
```

Effect remains a separate lazy transport, using postfix `!` to bridge carriers into effects:

```hem
let program = effect {
  let port = parsePort(rawPort)!
  let token = Option.fromNullish(env.TOKEN)!
  connect(port, token)!
}
```

Canonical `Err<E>` enters the Effect error channel as that exact `E`; canonical `None` becomes the
transport's checked `NoSuchElementError`. Native `?T!` is refused because nullish absence carries no
error policy. Effect 4's own eager carriers are `Option` and `Result<A, E>` (spelled
`Result.Result` through its namespace; the pre-release bare `Result` alias is gone). Postfix `!` on
them lowers to `Effect.fromOption`/`Effect.fromResult`, and checked Effect Result values still
construct and match with `Ok` and `Err`.

This provides a consistent model: focused package imports at the boundary, ordinary sync and async
carrier propagation in domain code, and one explicit adapter where the program enters Effect.
The [carrier reference](https://hem.run/reference/standard-carriers) lists every operation, the
frozen ABI, and third-party interoperability rules.

Source: https://hem.run/tour/option-and-result-the-boxed-ones/index.mdx
