# Chapter 8: Enums and pattern matching

An enum is a type whose values are one of a fixed set of variants, each of which may carry its own data. Together with `match`, enums are how Loup models "one of several kinds of thing": a parsed command, a connection state, a value that may be missing (`Option`), or an operation that may fail (`Result`). This chapter covers declaring and matching enums, `Option` and `Result`, and union types.

## Declaring and matching an enum

Variants are separated by commas. A variant may have a payload, declared like named parameters. Construct a variant with its enum's name, a dot, and the payload arguments, by name or by position:

```loup
@mode(host.native)

enum Shape {
    Circle(radius: float),
    Rectangle(width: float, height: float),
    Point,
}

func area(shape: Shape): float {
    return match shape {
        Shape.Circle(r) => 3.14159 * r * r,
        Shape.Rectangle(w, h) => w * h,
        Shape.Point => 0.0,
    };
}

func main(): void {
    let shapes = [Shape.Circle(radius = 1.0), Shape.Rectangle(width = 2.0, height = 3.0), Shape.Point];
    for (s in shapes) {
        println(area(s));
    }
    println(shapes[0]);
    println(Shape.Circle(1.0) == Shape.Circle(radius = 1.0));
}
```

*Listing 8-1: An enum with payloads*

```text
3.14159
6
0
Circle(1.0)
true
```

In a `match` arm, `Shape.Circle(r)` matches a circle and binds its payload, by position, to `r`. Enum values compare structurally: two values are equal when they are the same variant with equal payloads. `println` renders an enum value as its variant and payload; a native build in 1.25.1 refuses to render one whose payload is a tuple or a `Result` (or a deeper nesting such as `Option[list[…]]`) rather than print a pointer, so match on such values and print the parts.

Patterns nest, and a payload position may hold a literal, a binding, or `_`:

```loup
#~ @mode(host.native)
enum Event {
    Click(x: int, y: int, button: int),
    Key(code: int),
}

func describe(e: Event): string {
    return match e {
        Event.Click(0, 0, _) => "click at the origin",
        Event.Click(x, y, 1) => i"left click at {$ x $},{$ y $}",
        Event.Click(_, _, _) => "other click",
        Event.Key(27) => "escape",
        Event.Key(code) => i"key {$ code $}",
    };
}

func main(): void {
    println(describe(Event.Click(x = 0, y = 0, button = 1)));
    println(describe(Event.Click(x = 5, y = 7, button = 1)));
    println(describe(Event.Click(x = 5, y = 7, button = 3)));
    println(describe(Event.Key(code = 27)));
    println(describe(Event.Key(code = 65)));
}
```

*Listing 8-2: Literal patterns inside a variant*

```text
click at the origin
left click at 5,7
other click
escape
key 65
```

Arms are tried in order, so put the specific ones first. An arm may also carry a guard, `Event.Key(code) if (code < 32) => …`, which is checked after the pattern matches.

### Exhaustiveness

A `match` on an enum must handle every variant. When you add a variant, every `match` that does not handle it stops compiling, which is how a refactoring finds all the places it has to touch:

```loup,does_not_compile
@mode(host.native)

enum Shape {
    Circle(radius: float),
    Rectangle(width: float, height: float),
    Triangle(base: float, height: float),
}

func area(shape: Shape): float {
    return match shape {
        Shape.Circle(r) => 3.14159 * r * r,
        Shape.Rectangle(w, h) => w * h,
    };
}

func main(): void {
    println(area(Shape.Triangle(base = 4.0, height = 3.0)));
}
```

```text
main.lphc:10:12: error[E0001]: Non-exhaustive match: missing variants Triangle
```

Add the arm, `Shape.Triangle(b, h) => b * h / 2.0`. A `_ => …` arm also satisfies the check, but then the compiler can no longer tell you about the next variant you add, so prefer listing the variants.

> ⚠ **Planned.** Several pattern forms from the Language Reference do not parse in 1.25.1: alternatives (`Key(1) | Key(2) =>`), ranges (`0x20..0x7E =>`), whole-value bindings (`Event.Click(_, _, _) @ c =>`), and struct patterns (chapter 7). Use guards, and bind the matched value before the `match` if an arm needs all of it.

## A command parser

Enums shine when each kind of input carries different data. Here a line of text becomes one of three commands, and each command is applied to a key-value store:

```loup
@mode(host.native)

enum Command {
    Get(key: string),
    Set(key: string, value: string),
    Delete(key: string),
}

func parse(line: string): Result[Command, string] {
    let parts = line.split(" ");
    let verb = parts[0];
    if (verb == "get" and parts.length() == 2) {
        return Ok(Command.Get(key = parts[1]));
    }
    if (verb == "set" and parts.length() == 3) {
        return Ok(Command.Set(key = parts[1], value = parts[2]));
    }
    if (verb == "del" and parts.length() == 2) {
        return Ok(Command.Delete(key = parts[1]));
    }
    return Err(i"cannot parse: {$ line $}");
}

func apply(edits store: dict[string, string], cmd: Command): string {
    return match cmd {
        Command.Get(key) => store.get(key) ?? "(not set)",
        Command.Set(key, value) => {
            store[key] = value;
            "OK"
        },
        Command.Delete(key) => {
            store.remove(key);
            "OK"
        },
    };
}

func main(): void {
    var store: dict[string, string] = {};
    let script = ["set region eu-west", "get region", "del region", "get region", "frobnicate"];
    for (line in script) {
        match parse(line) {
            Ok(cmd) => println(apply(edits: store, cmd)),
            Err(message) => println(i"error: {$ message $}"),
        }
    }
}
```

*Listing 8-3: Parsing text into an enum and dispatching on it*

```text
OK
eu-west
OK
(not set)
error: cannot parse: frobnicate
```

An arm's body can be a block; its last expression is the arm's value, as with `if` expressions. `parse` returns a `Result`, covered next.

## `Option[T]`

`Option[T]` is a built-in enum with two variants, `Some(value)` and `None`. It is how Loup says "maybe a value", where C uses a null pointer and Python uses `None`. `T?` is shorthand for `Option[T]`:

```loup
@mode(host.native)

class User {
    pub let name: string;
    pub let manager: User?;
    new(name: string, manager: User?) {
        this.name = name;
        this.manager = manager;
    }
}

func findUser(users: list[User], name: string): User? {
    for (u in users) {
        if (u.name == name) {
            return Some(u);
        }
    }
    return None;
}

func main(): void {
    let ada = User("ada", None);
    let bob = User("bob", Some(ada));
    let staff = [ada, bob];

    let found = findUser(staff, "bob");
    println(found.is_some());
    println(bob.manager?.name ?? "nobody");
    println(ada.manager?.name ?? "nobody");
    let eve = findUser(staff, "eve");
    println(eve?.name ?? "no such user");

    match findUser(staff, "ada") {
        Some(u) => println(i"found {$ u.name $}"),
        None => println("not found"),
    }
}
```

*Listing 8-4: Working with `Option`*

```text
true
ada
nobody
no such user
found ada
```

`a?.b` reads `b` if `a` is `Some`, and is `None` otherwise; `x ?? fallback` takes the value out of an `Option` or uses the fallback. Together they replace chains of null checks. (In 1.25.1 a second `?.` in a chain, as in `found?.manager?.name`, does not type-check yet; go one level at a time with a `match` or `??`.) The methods on `Option` are `is_some()`, `is_none()`, `unwrap()`, `expect(message)` and `unwrap_or(fallback)`.

`unwrap()` asserts that the value is present. When it is not, the program stops:

```loup,throws
@mode(host.native)

func main(): void {
    let ports: dict[string, int] = {"http": 80};
    let ssh = ports.get("ssh").unwrap();
    println(ssh);
}
```

```text
Uncaught UnwrapError: called `unwrap()` on a None value
  at main (main.lphc:5:15)
```

Use `unwrap()` only where `None` would be a bug; for input that may legitimately be missing, use `??`, `unwrap_or`, or a `match`.

### Mistake: the method names from the specification

The Language Reference spells these methods in camel case (`isSome`, `unwrapOr`). The toolchain implements the snake-case names, and the camel-case ones are errors:

```loup,does_not_compile
@mode(host.native)

func main(): void {
    let timeout: int? = None;
    println(timeout.unwrapOr(30));
}
```

```text
main.lphc:5:13: error[E0001]: type 'Option[int]' has no method 'unwrapOr'
```

Write `timeout.unwrap_or(30)`. On a `Result` the same mistake is worse: it compiles and fails only when the line runs (see below).

## `Result[T, E]`

`Result[T, E]` is a built-in enum with `Ok(value)` and `Err(error)`. Return it from a function whose failure is an expected outcome the caller should handle, such as parsing input. Its methods are `is_ok()`, `is_err()`, `unwrap()`, `unwrap_err()`, `expect(message)` and `unwrap_or(fallback)`.

Inside a function that itself returns a `Result`, the postfix `?` operator unwraps an `Ok` or returns the `Err` to the caller:

```loup
@mode(host.native)

func parsePort(text: string): Result[int, string] {
    let n = text.toInt();
    if (n <= 0 or n > 65535) {
        return Err(i"bad port: {$ text $}");
    }
    return Ok(n);
}

func parseAddress(text: string): Result[string, string] {
    let parts = text.split(":");
    if (parts.length() != 2) {
        return Err(i"expected host:port, got {$ text $}");
    }
    let port = parsePort(parts[1])?;
    return Ok(i"{$ parts[0] $} port {$ port $}");
}

func main(): void {
    for (input in ["db:5432", "db:99999", "db"]) {
        match parseAddress(input) {
            Ok(addr) => println(addr),
            Err(e) => println(i"error: {$ e $}"),
        }
    }
}
```

*Listing 8-5: Propagating errors with `?`*

```text
db port 5432
error: bad port: 99999
error: expected host:port, got db
```

Chapter 12 compares `Result` with exceptions and covers `attempt`, which converts between them.

### Mistake: printing a `Result`

Printing an `Option` or an `Ok` prints the value inside. Printing an `Err` is an error at run time, so code that "just prints the result" works in testing and fails on the first bad input:

```loup,throws
@mode(host.native)

func parsePort(text: string): Result[int, string] {
    let n = text.toInt();
    if (n <= 0 or n > 65535) {
        return Err(i"bad port: {$ text $}");
    }
    return Ok(n);
}

func main(): void {
    println(parsePort("8080"));
    println(parsePort("99999"));
}
```

```text
8080
Uncaught RenderError: rendered an `Err` value: Err("bad port: 99999")
  at main (main.lphc:13:5)
```

`match` on the result, as in Listing 8-5, so the error case is handled on purpose.

### Mistake: `unwrapOr` on a `Result`

```loup,throws
@mode(host.native)

func main(): void {
    let retries: Result[int, string] = Err("RETRIES is not a number");
    println(retries.unwrapOr(3));
}
```

```text
Uncaught RuntimeError: internal interpreter limitation: unsupported instruction: CallVirtual unwrapOr on user class Result — method not found
```

Unlike on `Option`, the checker lets the camel-case name through on a `Result`, and the call fails when it runs. Write `retries.unwrap_or(3)`.

### Mistake: `?` on an `Option` inside a `Result` function

The specification says `?` works only on a `Result` (`E_PROPAGATE_NOT_RESULT`). Stage0 1.25.1 accepts it on an `Option` inside a function that returns `Result`, and turns a `None` into an `Err` whose payload is `None`, not the error message the caller expects:

```loup,not_desired_behavior
@mode(host.native)

func portFrom(env: dict[string, string]): Result[int, string] {
    let raw = env.get("PORT")?;
    return Ok(raw.toInt());
}

func main(): void {
    match portFrom({"HOST": "db"}) {
        Ok(n) => println(i"port {$ n $}"),
        Err(e) => println(i"error: [{$ e $}]"),
    }
}
```

```text
error: []
```

The error message is empty. Turn the missing value into a real error yourself:

```loup
#~ @mode(host.native)
func portFrom(env: dict[string, string]): Result[int, string] {
    match env.get("PORT") {
        Some(raw) => { return Ok(raw.toInt()); },
        None => { return Err("PORT is not set"); },
    }
}

func main(): void {
    match portFrom({"HOST": "db"}) {
        Ok(n) => println(i"port {$ n $}"),
        Err(e) => println(i"error: [{$ e $}]"),
    }
}
```

*Listing 8-6: Converting a missing value into an error*

```text
error: [PORT is not set]
```

In a function that returns `Option`, `?` is refused outright: `E_PROPAGATE_WRONG_RETURN: the '?' operator can only be used inside a function returning Result`.

## Union types

A union type such as `string | int` holds a value of any of its member types without declaring an enum (Language Reference §1-4.5). A `match` with type patterns, or an `is` test, narrows it:

```loup
#~ @mode(host.native)
func display(value: string | int | bool): string {
    return match value {
        string(s) => i"text: {$ s $}",
        int(n) => i"number: {$ n $}",
        bool(b) => if (b) { "yes" } else { "no" },
    };
}

func increment(value: string | int): string {
    if (value is string) {
        return value.toUpperCase();
    } else {
        return str(value + 1);
    }
}

func main(): void {
    println(display("hi"));
    println(display(4));
    println(display(true));
    println(increment("abc"));
    println(increment(41));
}
```

*Listing 8-7: Narrowing a union*

```text
text: hi
number: 4
yes
ABC
42
```

Narrowing applies inside the branches of the `if`; code after an `if` that returns early is not narrowed in 1.25.1, so keep the `else`. Prefer an enum when the cases have names and meaning (`Shape`, `Command`); use a union for an ad-hoc "either of these types" parameter. `T | None` is the same type as `Option[T]`.

Chapter 18 shows enums with lifetime parameters, whose variants can borrow from the input they were parsed from.

## Summary

- `enum E { A(x: T), B }` declares variants with optional payloads; construct with `E.A(x = v)` or `E.A(v)`. Enum values compare structurally.
- `match` destructures variants by position, accepts literals, bindings, `_` and `if (…)` guards, and must be exhaustive (`Non-exhaustive match: missing variants …`). Alternatives, ranges and `@` bindings are ⚠ planned.
- `Option[T]` (`T?`) is `Some(v)` or `None`; use `?.`, `??`, `match`, `unwrap_or`. `unwrap()` on `None` throws `UnwrapError`.
- `Result[T, E]` is `Ok(v)` or `Err(e)`; `?` propagates an `Err`. Printing an `Err` throws `RenderError`.
- The methods are snake case (`is_some`, `unwrap_or`); `?` on an `Option` in a `Result` function silently yields `Err(None)` in 1.25.1.
- Union types `A | B` narrow with `match` type patterns (`string(s) =>`) or `is`.
- Chapter 9 introduces classes and reference counting.
