# Union types (https://docs-dmpho5eos-ton-core-docs.vercel.app/llms/tolk/types/unions/content.md)



Tolk supports union types such as `T1 | T2 | T3`.

A union type represents a value that can be one of several types. [Pattern matching](/llms/languages/tolk/syntax/pattern-matching/content.md) is used to distinguish union variants. A special case `T | null` is written as `T?` and referred to as a [nullable type](/llms/languages/tolk/types/nullable/content.md).

```tolk
struct (0x12345678) Increment { /* ... */ }
struct (0x23456789) Reset { /* ... */ }

type IncomingMsg = Increment | Reset

fun handle(m: IncomingMsg) {
    match (m) {
        Increment => { /* here m is `Increment` */ }
        Reset =>     { /* here m is `Reset` */     }
    }
}
```

## Arbitrary union types [#arbitrary-union-types]

Union types are not limited to structures. Any types can be combined into a union. The following union types are valid:

* `int | slice`;
* `address | Point | null`;
* `Increment | Reset | coins`;
* `int8 | int16 | int32 | int64`.

Union types are automatically flattened:

```tolk
type Int8Or16 = int8 | int16

struct Demo {
    t1: Int8Or16 | int32?   // int8 | int16 | int32 | null
    t2: int | int           // int
}
```

Union types support assignment based on subtype relations. For example, a value of type `B | C` can be passed to or assigned to `A | B | C | D`:

```tolk
fun take(v: bits2 | bits4 | bits8 | bits16) {}

fun demo() {
    take(someSlice as bits4);    // ok
    take(anotherV);              // ok for `bits2 | bits16`
}
```

## Exhaustive pattern matching [#exhaustive-pattern-matching]

A `match` expression must cover all possible cases. It must be exhaustive.

```tolk
fun errDemo(v: int | slice | Point) {
    match (v) {
        slice => { v.loadAddress() }
        int => { v * 2 }
        // error: missing `Point`
    }
}
```

`match` can be used with nullable types, since `T?` is equivalent to `T | null`. It can also be used as an expression:

```tolk
fun replaceNullWith0(maybeInt: int?): int {
    return match (maybeInt) {
        null => 0,
        int => maybeInt,
    }
}
```

## Union type inference errors [#union-type-inference-errors]

Auto-inference of a union type results in an error. If `match` arms produce values of different types, the inferred result is a union, which is typically not intended:

```tolk
var a = match (...) {
    ... => beginCell(),
    ... => 123,
};
```

The type of `a` is inferred as `builder | int`. In most cases, this indicates an error in the code. In such cases, the compiler emits the following message:

```ansi
error: type of `match` was inferred as `builder | int`; probably, it's not what you expected
assign it to a variable `var a: <type> = match (...) { ... }` manually
```

To resolve this, either explicitly declare `a` as a union type or fix the code if the union is unintended.

The same rule applies to other cases, such as return type inference:

```tolk
fun f() {
    if (...) { return someInt64 }
    else { return someInt32 }
}
```

The result is inferred as `int32 | int64`. While it is valid, a single integer type is usually expected. The compiler reports an error. To fix it, explicitly declare the return type:

```tolk
fun f(): int {
    if (...) { return someInt64 }
    else { return someInt32 }
}
```

Declaring return types is recommended practice.

## `is` and `!is` operators [#is-and-is-operators]

Union types can be tested using the `is` operator:

```tolk
fun demo(v: A | B) {
    if (v is A) {
        v.aMethod();
    } else {
        v.bMethod();
    }
}
```

## `lazy` matching for unions [#lazy-matching-for-unions]

In [message-handling](/llms/languages/tolk/features/message-handling/content.md), union values are commonly parsed using `lazy`:

```tolk
fun onInternalMessage(in: InMessage) {
    val msg = lazy MyUnion.fromSlice(in.body);
    match (msg) {
        // ...
    }
}
```

This pattern is referred to as [`lazy` match](/llms/languages/tolk/features/lazy-loading/content.md):

1. No union is allocated on the stack upfront; loading is deferred until needed.
2. `match` operates by inspecting the slice prefix (opcode), instead of checking a type identifier on the stack.

Union types continue to work correctly without `lazy` and follow the same type-system rules.

## `void` as a union variant [#void-as-a-union-variant]

`T | void` is a valid serializable union meaning "T or empty slice" — `void` carries no bits and does not participate in the prefix tree. This is useful for chains like wallet-v5's payload format: parse cells until refs run out. See [void-never](/llms/languages/tolk/types/void-never/content.md).

## Stack layout and serialization [#stack-layout-and-serialization]

Union types have a complex [stack layout](/llms/languages/tolk/types/overall-tvm-stack/content.md), commonly referred to as tagged unions.

[Serialization](/llms/languages/tolk/types/overall-serialization/content.md) depends on whether the union consists of structures with manual serialization prefixes:

* if yes; for example, `struct (0x1234) A`, those prefixes are used;
* if no, the compiler automatically generates a prefix tree; for example, `T1 | T2` is called `Either` type: `0` + `T1` or `1` + `T2`.
