# Records, enums, match

A record is a shape. An enum is a choice. `match` covers every case, and the compiler counts them.

## Records

A record type lists fields. Compatibility is by shape: a value with more fields fits where fewer are wanted, with no cast and no base class. This is width subtyping, and it is the whole inheritance story.

Fields of a `mut` binding are assigned in place. A record that owns heap data — a `String` field, for example — is moved by an assignment, so `copy()` is how both sides keep a value.

```vyrn
// A record type is a shape. A value with more fields fits where fewer are
// wanted, with no cast and no inheritance.
import { joinWith } from "std/strings"

type Named = { name: String }
type User = { name: String, age: Int64 }

fn greet(who: Named) -> String {
    return "hello, \{who.name}"
}

export fn demo() -> String {
    let u = User { name: "Ada", age: 36 }
    let mut next = u.copy() // a copy, because both `u` and `next` are read below
    next.age = next.age + 1

    return joinWith([greet(u), "was \{u.age}", "now \{next.age}"], "\n")
}

fn main() -> Int64 {
    print(demo())
    return 0
}
```

Built output:

```
hello, Ada
was 36
now 37
```

## Enums

An enum value is exactly one variant, and a variant can carry a payload. There is no tag field to keep in step with a union, because the tag is the type.

`match` must cover every variant. Leave one out and the file does not compile. That is what makes an enum safe to extend: add a variant, and the compiler lists every place that has to answer for it.

```vyrn
// A value is exactly one variant. A variant may carry a payload, and `match`
// must cover every one — leave an arm out and the file does not compile.
import { joinWith } from "std/strings"

type Shape = enum {
    Circle(Int64),
    Rect(Int64, Int64),
    Point }

fn describeShape(s: Shape) -> String {
    return match s {
        Circle(r) => "circle of radius \{r}",
        Rect(w, h) => "rect \{w} by \{h}",
        Point => "a point",
    }
}

export fn demo() -> String {
    let shapes: Array<Shape> = [Circle(2), Rect(3, 4), Point]
    let mut out: Array<String> = []
    for s in shapes {
        out.push(describeShape(s))
    }
    return joinWith(out, "\n")
}

fn main() -> Int64 {
    print(demo())
    return 0
}
```

Built output:

```
circle of radius 2
rect 3 by 4
a point
```

## match expressions

`match` answers with a value, so a binding takes it directly and there is no mutable slot to fill in each arm.

`if let` reads one variant when the others need no arm. `while let` does the same in a loop, and stops when the pattern stops matching.

```vyrn
// `match` is an expression: it answers with a value, so a binding can take it
// directly. `if let` reads one variant when the others need no arm of their own.
import { joinWith, indexOf } from "std/strings"

type Level = enum {
    Info,
    Warn(String) }

fn found(haystack: String, needle: String) -> String {
    if let Some(at) = indexOf(haystack, needle) {
        return "\{needle} at byte \{at}"
    }
    return "\{needle} is not there"
}

export fn demo() -> String {
    let l: Level = Warn("disk is filling")
    let line = match l {
        Info => "nothing to say",
        Warn(what) => "warning: \{what}",
    }
    return joinWith([line, found("hello, world", "world"), found("hello, world", "zebra")], "\n")
}

fn main() -> Int64 {
    print(demo())
    return 0
}
```

Built output:

```
warning: disk is filling
world at byte 7
zebra is not there
```

---

From https://vyrn-lang.github.io/guide/records.html — generated by `site/export.vyrn` from the Vyrn repository.
