# Enums, Tagged Unions and Exhaustive switch — Zig

Source: https://www.geekswithgeeks.com/en/zig/t-unions

> Model alternatives with enums and tagged unions.

## Sum types in Zig

An **enum** defines a set of named values: `const Status = enum { placed, paid, shipped, cancelled };`. Enums can have an explicit integer tag type (`enum(u8)`), explicit values, and **methods**. Enum literals are written with a leading dot (`.paid`), and `@tagName` returns the name as a string. A **tagged union** (`union(enum)`) holds **one of several typed payloads** plus a tag saying which, like Rust's enums or Haskell's sum types: `const Payment = union(enum) { card: [4]u8, upi: []const u8, cash };`. Use **`switch`** with **payload captures** (`.upi => |vpa| ...`) to handle each case; the switch must be **exhaustive**, so adding a new variant produces compile errors everywhere it is not handled. Accessing the wrong field of a tagged union panics in safe modes. Bare (untagged) `union`s and `extern union`s exist for low-level memory layout and C interop. Tagged unions are the idiomatic way to model **state machines**, messages, AST nodes and results with different shapes, making illegal states unrepresentable.

## Payments and order states as tagged unions

Exhaustive switches with payload captures.

```zig
const std = @import("std");

const Payment = union(enum) {
    card: [4]u8,               // last four digits
    upi: []const u8,           // VPA such as asha@okbank
    cash,                      // no payload (void)
};

const OrderState = union(enum) {
    placed,
    paid: Payment,
    shipped: struct { payment: Payment, tracking: []const u8 },
    cancelled: []const u8,     // reason

    fn label(self: OrderState) []const u8 {
        return switch (self) {
            .placed => "placed",
            .paid => "paid",
            .shipped => "shipped",
            .cancelled => "cancelled",
        };
    }
};

fn describePayment(p: Payment) void {
    switch (p) {
        .card => |last4| std.debug.print("card ending {s}\n", .{&last4}),
        .upi => |vpa| std.debug.print("UPI {s}\n", .{vpa}),
        .cash => std.debug.print("cash on delivery\n", .{}),
    }
}

pub fn main() void {
    var state: OrderState = .placed;
    state = .{ .paid = .{ .upi = "asha@okbank" } };
    std.debug.print("state: {s}\n", .{state.label()});

    switch (state) {
        .paid => |payment| describePayment(payment),
        .shipped => |s| std.debug.print("tracking {s}\n", .{s.tracking}),
        .placed, .cancelled => {},
    }

    const Status = enum(u8) { placed = 1, paid, shipped };
    std.debug.print("{s} = {d}\n", .{ @tagName(Status.paid), @intFromEnum(Status.paid) });   // paid = 2
}
```

## A parcel with a label

A tagged union is a parcel with a label on the outside saying what is inside: a card slip, a UPI note or nothing for cash. You must read the label (switch) before opening it, and the courier refuses parcels with labels nobody handles.

**Quiz:** What happens when you add a new variant to a tagged union that is switched on without an else prong?

- [ ] Nothing
- [ ] It is handled by the first prong
- [ ] It panics at run time
- [x] Every non-exhaustive switch becomes a compile error until it handles the new case

*Answer:* Every non-exhaustive switch becomes a compile error until it handles the new case. Exhaustive switches make the compiler point to every place to update.
