---
title: "Variant, brand, and error declarations"
description: "Payload forms, emitted shapes, brand rules, error classes, and the deriving Eq relation."
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.

# Variant, brand, and error declarations

Three declaration forms extend `type`. All three emit ordinary TypeScript that is the public ABI: consumers outside Hem see unions, constructor functions, and classes.

## Variants

```hem
type Shape = | Circle({ r: number }) | Rect({ w: number, h: number }) | Point
```

The discriminant is always `_tag`. Emit is a readonly discriminated union plus one constructor per member.

### Payload forms

| Form | Declaration | Emitted member | Constructor |
| --- | --- | --- | --- |
| None | `Point` | `{ readonly _tag: "Point" }` | A shared singleton constant |
| One positional | `Ident(string)` | `{ readonly _tag: "Ident"; readonly value: string }` | Takes the value |
| Record | `Paid({ at: Date })` | `{ readonly _tag: "Paid"; readonly at: Date }` | Takes a fields object |

Record payload fields spread onto the variant object. No detached payload object exists at runtime, which is why a record pattern binds the narrowed variant itself, `_tag` included.

Multiple positional payloads refuse with HEM2017.

### Type parameters

A variant takes type parameters like any other declaration. Constructors infer them from context, and the parameters survive into `.d.ts` output.

```hem
export type Result<T, E> = | Ok(T) | Err(E)
```

## Brands

```hem
type UserId = brand string
```

Emits `string & { readonly __brand: "UserId"; readonly __identity: unique symbol }` plus an identity constructor `UserId(value: string): UserId`. The constructor is the identity function at runtime and provides a searchable construction boundary, mirroring variant constructors.

| Property | Rule |
| --- | --- |
| Base type | Any non-generic type, including a union |
| Unwrapping | Free: a branded type is a subtype of its base |
| Construction | Required: a bare base value is not assignable to the brand |
| Pattern form | None |
| Type parameters | Refused (HEM2020) |
| Diagnostic rendering | The brand name, not the expanded intersection |

`brand` is contextual in exactly one position, immediately after a type declaration's `=`. Everywhere else it is an ordinary identifier, so `type X = brand` with nothing following aliases a type named `brand`.

Independent declarations remain distinct even when their names and base types match. Imports, aliases, and reexports of the same declaration preserve its identity. Installed package copies follow TypeScript's declaration resolver, including package-name/version unification. This intentionally revises the earlier name-only contract; no runtime wrapper or Hem identity registry is involved.

## Errors

```hem
error QuotaExceeded({ orgId: string })
```

Declares a nominal tagged error class carrying `_tag` and extending `Error`. Declared `cause` and `message` fields forward to `super`. `export error` exports the class.

Under the Effect extension the same declaration emits `Data.TaggedError`, which also extends `Error` and also carries `_tag`. The two emits are shape-compatible, so `instanceof` and `_tag` matching work against both and modules compiled with and without the extension exchange errors freely.

## `deriving Eq`

A variant or error declaration may carry a trailing `deriving Eq` clause. It generates a comparator named `eq<Name>` beside the constructors, with visibility mirroring the declaration's own.

```hem
type Tree = | Leaf(number) | Node({ left: Tree, right: Tree }) deriving Eq
```

The relation is recursive SameValueZero over declared data fields. SameValueZero is `===` with one correction: `NaN` equals itself, and `-0` equals `0`.

### What the walk descends into

| Field type | Comparison |
| --- | --- |
| `string`, `boolean`, `bigint` | `===` |
| `number` | SameValueZero |
| `T[]` | Length check, then index-wise recursion through a shared per-file `eqArray` helper |
| Tuples | Position-wise |
| `?T` | Both absent is equal; one absent is unequal; otherwise recurse |
| A brand | Resolved to its underlying primitive first |
| Another variant or error that derives `Eq` | Calls that type's own comparator, self-reference included |
| Anything else | Reference identity |

Functions, `Date`, `RegExp`, `Map`, `Set`, and imported classes fall into the last row. Two distinct `Date` values with the same timestamp are not equal under `deriving Eq`: the relation is structural over shapes the compiler owns, not a deep-equality library.

### Dispatch and generics

A variant compares `_tag` first, then only the matched tag's declared fields, narrowing the second operand with one `as Extract<…>` cast. An error has one shape and no tag, so its comparator compares the declared fields only, never `message`, `cause`, `name`, or `stack`.

A generic variant emits a generic comparator taking one extra trailing comparator parameter per type parameter that appears in payload position.

### Scope and refusals

Only variant and error declarations accept the clause. A plain `type` alias has no constructors to sit beside and no closed field set to walk.

HEM2023 covers two refusals: an unsupported deriving name, and a field typed as a Hem variant or error that does not itself derive `Eq`. A genuinely foreign type compares by reference with no diagnostic, which is the documented structural boundary.

## Normative source

Specification 0.2, §8 (declarations), §9 (errors), §3 (types). The full `deriving Eq` conformance checklist and worked examples are specified alongside it.

Source: https://hem.run/reference/variants-and-brands/index.mdx
