---
title: "Absence: ?T"
description: "The ?T type, where it comes from, the eliminators that discharge it, and how Some and None resolve."
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.

# Absence: ?T

Use `?T` for everyday absence in Hem-owned application models. `?T` is sugar for `T | null | undefined`. It is Hem's native, allocation-free absence
representation: there is no wrapper object at runtime and the emitted type is the union itself.

When preserving an external contract, use its exact `T | null` or `T | undefined` type, or reuse its exported type alias. Exact unions are writable throughout Hem, including within generic and conditional type expressions; this interop emphasis is authoring guidance, not a compiler restriction. They preserve which sentinel the contract admits. `?T` deliberately admits both; it must not replace a one-sentinel public contract. Nullish value literals remain refused; construct absence with contextual `None`. Canonical boxed
`Option<T>` is a separate explicit standard-module carrier for nesting, combinators, must-use
signaling, and stable tagged transport; see [Standard Option and Result](/reference/standard-carriers).

## Sources of nullish values

| Source | Example | Resulting type |
| --- | --- | --- |
| A nullable TypeScript API | `document.getElementById(id)` | `Element \| null` |
| A lookup that may miss | `map.get(key)` | `V \| undefined` |
| Array and record indexing | `xs[i]` | `T \| undefined` |
| An optional parameter | `(x?: T) => …` | `x` is `T \| undefined` in the body |
| An `if` without `else` in value position | `if ok { value }` | `T \| undefined` |

Indexing adds undefined to the value type because Hem's checker requires `noUncheckedIndexedAccess`. Unchecked indexing is not writable.

## Eliminators

These forms discharge nullish values, including exact one-sentinel unions.

| Form | Spelling | Notes |
| --- | --- | --- |
| Match | `match v { \| Some(x) => … \| None => … }` | Both cases checked for exhaustiveness |
| Optional chaining | `v?.field` | TypeScript's operator, unchanged |
| Defaulting | `v ?? fallback` | TypeScript's operator, unchanged |
| Guard binding | `let Some(x) = v else return …` | Ordinary local recovery; the `else` branch must diverge (HEM2014) |
| Carrier binding | `let Some(x) = v` | In a compatible nullish-returning arrow, propagates the exact sentinel |
| Comparison | `if v != None { … }` | Narrows `v` in the true branch |
| Optional call | `callback?.(arg)` | Result is `R \| undefined` for an `R`-returning callback |

The optional call evaluates its receiver or getter once. Arguments evaluate only when the callback is present, and a member callback keeps its `this` receiver. Void-returning, Promise-returning, and generic callbacks follow the same rule.

The refutable pattern set accepted by `let`-`else` is `Some`, constructors, literals, records, arrays, and `as` aliases. The same set heads `while let`, which re-tests the pattern each iteration.

## How `Some` and `None` resolve

`Some` and `None` are type-directed in both pattern and expression position.

As patterns on a nullish scrutinee they lower to nullish checks. As expressions in a nullish context, `Some(e)` is `e` itself, and `None` constructs absence in the expected type: it emits `null` when null is admitted and undefined is not, and `undefined` when undefined is admitted. This selects a value for a new construction; it does not normalize existing values.

```hem
node.textContent = None          // emits null; the DOM setter accepts string | null
let found: ?Element = None       // emits undefined
```

`Some(x)` written with no expected type refuses with HEM2025. Bare `None` infers undefined. Choose a representation with an explicit annotation:

| Checked context | `Some(value)` | `None` |
| --- | --- | --- |
| `T \| null` | The value itself | null |
| `T \| undefined` or `?T` | The value itself | undefined |
| Canonical `Option<T>` | A tagged Some object | A tagged None object |
| Configured Effect Option | The transport's Some constructor | The transport's None constructor |

In a nullish context, `Some(value)` does not assert presence. It inserts no wrapper: a nullish payload remains absent when matched. To construct a boxed value explicitly, import canonical `Option` and use `Option.some(value)` or `Option.none`. A lexically bound `Some` or `None`, including a user variant constructor, keeps its ordinary meaning and takes precedence over contextual vocabulary.

On canonical boxed Option or configured Effect Option, `Some` and `None` instead follow that
checked carrier's tag and payload representation. The spellings are shared; the ABIs are not.

## Changing an annotation can change construction

An annotation affects what contextual `None` constructs. Widening `number | null` to `?number` admits undefined as well, so the same `None` expression now emits undefined:

```hem
let exact: number | null = None   // constructs null
let unified: ?number = None       // constructs undefined
let preserved: ?number = exact    // retains the existing null
```

Treat an annotation edit around `None` as a possible runtime change, especially at an external boundary. `None` is contextual absence, not a fixed JavaScript literal. When both native sentinels are admitted, construction consistently chooses undefined; bare `None` also infers undefined.

Passing an existing value through an assignment or a compatible function boundary does not perform this selection again. A carrier binding such as `let Some(item) = value` likewise returns the original absent value unchanged: an observed null remains null, and an observed undefined remains undefined. Constructing a new `None` instead of returning the observed value can change that result.

## Equality against `None`

`==` and `!=` against `None` emit `== null` and `!= null`. This is the only loose equality Hem emits, and it is deliberate: one evaluation eliminates both `null` and `undefined`.

Everywhere else `==` emits `===`.

## Predicates over absence

Prefer a checked predicate inferred from the body, such as `(value: unknown) => value == None`. Authored `is` or `asserts` signatures do not gain permission from the nullish type syntax: unchecked narrowing promises still refuse with HEM2029.

## Limits and refusals

A literal `??T` does not exist and refuses with HEM2003.

Generic instantiation collapses. `Map<string, ?number>.get` cannot distinguish "key absent" from "key present, value undefined", because both are the same unboxed union. Similarly, a generic `type Maybe<T> = ?T` instantiated with an already nullish `T` adds no nesting. Use boxed `Option<Option<T>>` for three distinct states: outer None, Some containing inner None, and Some containing inner Some.

`?T` is for absence that Hem owns. An externally owned `T | null` or `T | undefined` contract is not the same set: widening it to `?T` changes the public API. Write the exact union directly in Hem, or reuse an exported library type. Check emitted declarations and an unchanged strict TypeScript caller to verify the public contract.

## Normative source

Specification 0.2, §10 (optionals and carriers), §6 (operators), §4 (bindings), §7 (`while let`).

Source: https://hem.run/reference/absence/index.mdx
