# Modeling State Machines

### Modeling State Machines

A state machine is a simple way to describe a program that moves between fixed states.

For example, a network connection may be:

```text
disconnected
connecting
connected
closing
```

At any moment, it is in exactly one state. It is not both `connected` and `closing`. It is one state, then another state, then another.

This is exactly the kind of problem where Zig enums and tagged unions are useful.

#### Start with an Enum

If each state only needs a name, use an enum.

```zig
const ConnectionState = enum {
    disconnected,
    connecting,
    connected,
    closing,
};
```

Now a value of type `ConnectionState` must be one of those four states.

```zig
var state: ConnectionState = .disconnected;
```

You can change it later:

```zig
state = .connecting;
state = .connected;
```

This is already safer than using strings or integers.

```zig
var state = "connected";
```

A string can contain a typo.

```zig
var state = "conected";
```

The compiler cannot know that this is wrong.

With an enum, a typo is rejected:

```zig
var state: ConnectionState = .conected; // error
```

#### Use `switch` to Handle States

A state machine usually has behavior for each state.

```zig
fn canSend(state: ConnectionState) bool {
    return switch (state) {
        .disconnected => false,
        .connecting => false,
        .connected => true,
        .closing => false,
    };
}
```

This function answers one question: can we send data in this state?

Only the `connected` state returns `true`.

The `switch` is exhaustive. It handles every possible `ConnectionState`.

That gives you a useful safety property. If you add a new state later, Zig can force you to update the switch.

#### State Transitions

A state transition is a move from one state to another.

For example:

```text
disconnected -> connecting
connecting -> connected
connected -> closing
closing -> disconnected
```

You can write that as a function:

```zig
const ConnectionState = enum {
    disconnected,
    connecting,
    connected,
    closing,
};

fn next(state: ConnectionState) ConnectionState {
    return switch (state) {
        .disconnected => .connecting,
        .connecting => .connected,
        .connected => .closing,
        .closing => .disconnected,
    };
}
```

Use it like this:

```zig
var state: ConnectionState = .disconnected;

state = next(state); // connecting
state = next(state); // connected
state = next(state); // closing
```

The state changes are explicit. There is no hidden logic.

#### Some Transitions Should Be Rejected

Real state machines often have rules.

For example, a connection may allow these transitions:

```text
disconnected -> connecting
connecting -> connected
connecting -> disconnected
connected -> closing
closing -> disconnected
```

But this should not be allowed:

```text
disconnected -> connected
```

You can encode that rule with an error union.

```zig
const TransitionError = error{
    InvalidTransition,
};

fn transition(
    current: ConnectionState,
    next_state: ConnectionState,
) TransitionError!ConnectionState {
    return switch (current) {
        .disconnected => switch (next_state) {
            .connecting => next_state,
            else => error.InvalidTransition,
        },

        .connecting => switch (next_state) {
            .connected, .disconnected => next_state,
            else => error.InvalidTransition,
        },

        .connected => switch (next_state) {
            .closing => next_state,
            else => error.InvalidTransition,
        },

        .closing => switch (next_state) {
            .disconnected => next_state,
            else => error.InvalidTransition,
        },
    };
}
```

The return type says the function may fail:

```zig
TransitionError!ConnectionState
```

That means: either return the new state, or return `error.InvalidTransition`.

#### Using the Transition Function

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

const ConnectionState = enum {
    disconnected,
    connecting,
    connected,
    closing,
};

const TransitionError = error{
    InvalidTransition,
};

fn transition(
    current: ConnectionState,
    next_state: ConnectionState,
) TransitionError!ConnectionState {
    return switch (current) {
        .disconnected => switch (next_state) {
            .connecting => next_state,
            else => error.InvalidTransition,
        },

        .connecting => switch (next_state) {
            .connected, .disconnected => next_state,
            else => error.InvalidTransition,
        },

        .connected => switch (next_state) {
            .closing => next_state,
            else => error.InvalidTransition,
        },

        .closing => switch (next_state) {
            .disconnected => next_state,
            else => error.InvalidTransition,
        },
    };
}

pub fn main() void {
    var state: ConnectionState = .disconnected;

    state = transition(state, .connecting) catch {
        std.debug.print("invalid transition\n", .{});
        return;
    };

    state = transition(state, .connected) catch {
        std.debug.print("invalid transition\n", .{});
        return;
    };

    std.debug.print("state = {}\n", .{state});
}
```

Output:

```text
state = main.ConnectionState.connected
```

The exact printed name may include the type path, but the important value is `connected`.

#### Keep State and Data Together

Sometimes a state needs extra data.

For example:

```text
disconnected
connecting, with attempt number
connected, with peer address
closing
```

An enum alone cannot store that extra data.

Use a tagged union.

```zig
const Connection = union(enum) {
    disconnected,
    connecting: u8,
    connected: []const u8,
    closing,
};
```

This says:

```text
disconnected carries no data
connecting carries a u8 attempt number
connected carries a peer address
closing carries no data
```

Now the state and its data stay together.

#### Handling State with Payloads

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

const Connection = union(enum) {
    disconnected,
    connecting: u8,
    connected: []const u8,
    closing,

    fn print(self: Connection) void {
        switch (self) {
            .disconnected => {
                std.debug.print("disconnected\n", .{});
            },
            .connecting => |attempt| {
                std.debug.print("connecting, attempt {}\n", .{attempt});
            },
            .connected => |peer| {
                std.debug.print("connected to {s}\n", .{peer});
            },
            .closing => {
                std.debug.print("closing\n", .{});
            },
        }
    }
};

pub fn main() void {
    var conn = Connection.disconnected;

    conn = Connection{ .connecting = 1 };
    conn.print();

    conn = Connection{ .connected = "127.0.0.1:9000" };
    conn.print();

    conn = Connection.closing;
    conn.print();
}
```

Output:

```text
connecting, attempt 1
connected to 127.0.0.1:9000
closing
```

This model is stronger than keeping state and data in separate fields.

#### Avoid Loose Optional Fields

A weak model might look like this:

```zig
const Connection = struct {
    state: ConnectionState,
    attempt: ?u8 = null,
    peer: ?[]const u8 = null,
};
```

This can represent invalid combinations:

```zig
const conn = Connection{
    .state = .disconnected,
    .attempt = 3,
    .peer = "127.0.0.1:9000",
};
```

A disconnected connection should not have a peer address.

A tagged union avoids that:

```zig
const Connection = union(enum) {
    disconnected,
    connecting: u8,
    connected: []const u8,
    closing,
};
```

Now each state carries only the data that belongs to that state.

#### Model Events Separately

Many state machines respond to events.

For a connection, events might be:

```text
start
connected
failed
close
closed
```

Use another enum or tagged union for events.

```zig
const Event = enum {
    start,
    connected,
    failed,
    close,
    closed,
};
```

Then write a function that combines current state and event:

```zig
fn apply(state: ConnectionState, event: Event) ?ConnectionState {
    return switch (state) {
        .disconnected => switch (event) {
            .start => .connecting,
            else => null,
        },

        .connecting => switch (event) {
            .connected => .connected,
            .failed => .disconnected,
            else => null,
        },

        .connected => switch (event) {
            .close => .closing,
            else => null,
        },

        .closing => switch (event) {
            .closed => .disconnected,
            else => null,
        },
    };
}
```

The return type is:

```zig
?ConnectionState
```

That means: either a new state, or `null` if the event is not valid in the current state.

#### Complete Event-Driven Example

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

const ConnectionState = enum {
    disconnected,
    connecting,
    connected,
    closing,
};

const Event = enum {
    start,
    connected,
    failed,
    close,
    closed,
};

fn apply(state: ConnectionState, event: Event) ?ConnectionState {
    return switch (state) {
        .disconnected => switch (event) {
            .start => .connecting,
            else => null,
        },

        .connecting => switch (event) {
            .connected => .connected,
            .failed => .disconnected,
            else => null,
        },

        .connected => switch (event) {
            .close => .closing,
            else => null,
        },

        .closing => switch (event) {
            .closed => .disconnected,
            else => null,
        },
    };
}

pub fn main() void {
    var state: ConnectionState = .disconnected;

    const events = [_]Event{
        .start,
        .connected,
        .close,
        .closed,
    };

    for (events) |event| {
        state = apply(state, event) orelse {
            std.debug.print("invalid event: {}\n", .{event});
            return;
        };

        std.debug.print("state = {}\n", .{state});
    }
}
```

This walks through a valid sequence:

```text
disconnected -> connecting -> connected -> closing -> disconnected
```

#### Put the Logic Near the Type

You can place the transition logic inside a struct or enum namespace.

```zig
const ConnectionState = enum {
    disconnected,
    connecting,
    connected,
    closing,

    fn canSend(self: ConnectionState) bool {
        return switch (self) {
            .disconnected => false,
            .connecting => false,
            .connected => true,
            .closing => false,
        };
    }
};
```

Then call:

```zig
const ok = state.canSend();
```

This keeps state-related behavior close to the state definition.

For larger machines, you may use a struct:

```zig
const ConnectionMachine = struct {
    state: ConnectionState,

    pub fn init() ConnectionMachine {
        return .{
            .state = .disconnected,
        };
    }

    pub fn apply(self: *ConnectionMachine, event: Event) bool {
        const next_state = applyState(self.state, event) orelse return false;
        self.state = next_state;
        return true;
    }
};
```

The struct stores the current state. Its methods change that state safely.

#### Prefer Explicit States

Do not hide important states inside booleans.

This is weak:

```zig
const Connection = struct {
    is_connected: bool,
    is_connecting: bool,
};
```

It can represent invalid combinations:

```text
is_connected = true
is_connecting = true
```

A connection should not usually be both connected and connecting.

An enum is clearer:

```zig
const ConnectionState = enum {
    disconnected,
    connecting,
    connected,
    closing,
};
```

One value, one state.

#### Prefer Explicit Events

Do not pass vague strings as events:

```zig
handleEvent("connected");
```

Use an enum:

```zig
handleEvent(.connected);
```

This lets the compiler check spelling and valid cases.

If an event has data, use a tagged union:

```zig
const Event = union(enum) {
    start,
    connected: []const u8,
    failed: ErrorCode,
    close,
};
```

Now event data is attached to the correct event.

#### The Main Idea

A state machine becomes easier to write when states are explicit.

Use enums when states are just names:

```zig
const State = enum {
    idle,
    running,
    stopped,
};
```

Use tagged unions when states carry data:

```zig
const State = union(enum) {
    idle,
    running: JobId,
    stopped,
};
```

Use `switch` to handle every state. Put transition rules in one place. Let the type system prevent invalid combinations.

