> ## Documentation Index
> Fetch the complete documentation index at: https://forst-lang.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Name a reusable rule

> Give a domain rule a name, then reuse it at every check.

A **type guard** names a rule about a value. When the guard passes, Forst remembers
what the check proved. Later lines can use required fields without repeating
the same validation.

Use a guard for rules such as “this user is logged in” or “this request has
input.” A failed `ensure` that uses the guard stops the function before the
rest of the body runs.

## Define a reusable check

This `LoggedIn` guard requires an `AppContext` that has both a session and a
user.

<Tabs>
  <Tab title="Forst">
    ```ft theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
    type User = {
    	name: String,
    }

    type AppContext = {
    	sessionId: *String,
    	user: *User,
    }

    is (ctx AppContext) LoggedIn() {
    	ensure ctx.sessionId is Present()
    	ensure ctx.user is Present()
    }
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
    // forst build
    type AppContext struct {
    	sessionId *string
    	user      *User
    }

    type User struct {
    	name string
    }

    func G_HRerS5U4F3k(ctx AppContext) bool {
    	if ctx.sessionId == nil {
    		return false
    	}
    	if ctx.user == nil {
    		return false
    	}
    	return true
    }
    ```
  </Tab>
</Tabs>

The name in parentheses is the value you check (`ctx`). The type after it is
the type the guard attaches to (`AppContext`). The name after the parentheses
is the rule (`LoggedIn`).

Use that name anywhere you can write an `is` check:

```ft theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
ensure ctx is LoggedIn()
```

After this line, Forst knows that `ctx.sessionId` and `ctx.user` are present.

A type guard records what a passing check proves about the value. A `Bool`
return cannot do that. When `LoggedIn()` passes, later code can use the
session and user as present.

## Attach a guard to a named type

Attach a guard to a declared named type or struct shape. Define a named scalar
type when you want guards on values such as passwords:

```ft theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
type Password = String

is (password Password) Strong() {
	ensure password is Min(12)
	ensure password is Contains("#")
}
```

`Strong` then belongs to `Password`, not to every `String` in the program.

### When to use which form

| Need | Use |
| - | - |
| Length, bounds, prefix, and similar fixed checks | A [built-in constraint](/docs/language/shapes-and-constraints) on the type |
| The same bound as a reusable type name | A named constrained alias (`type HttpPort = Int.Min(1).Max(65535)`) |
| A domain rule with several steps or field presence | A **type guard** (`is (ctx AppContext) LoggedIn()`) |

## Use a guard inside `if` and `ensure`

Use `if` when both outcomes belong in the function. Use `ensure` with a guard
inside a branch when that branch only makes sense if more checks pass.

This handler ships a paid order. An unpaid order waits. A paid order must be
unlocked and must have items:

<Tabs>
  <Tab title="Forst">
    ```ft theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
    error OrderLocked { orderId: String }
    error EmptyCart { orderId: String }

    type Order = {
    	id:     String,
    	paid:   Bool,
    	locked: Bool,
    	items:  []String,
    }

    is (order Order) Unlocked() {
    	ensure order.locked is False()
    }

    is (order Order) HasItems() {
    	ensure order.items is Min(1)
    }

    func fulfill(order Order): Result(String, Error) {
    	if order.paid {
    		ensure order is Unlocked() else OrderLocked{ orderId: order.id }
    		ensure order is HasItems() else EmptyCart{ orderId: order.id }
    		return order.items[0]
    	}
    	return "awaiting payment"
    }
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
    // forst build
    type EmptyCart struct {
    	orderId string
    }

    type Order struct {
    	id     string
    	items  []string
    	locked bool
    	paid   bool
    }

    type OrderLocked struct {
    	orderId string
    }

    func (e EmptyCart) Error() string { return "error" }
    func (e OrderLocked) Error() string { return "error" }

    func (e EmptyCart) ForstErrorTag() string { return "main/EmptyCart" }
    func (e OrderLocked) ForstErrorTag() string { return "main/OrderLocked" }

    func fulfill(order Order) (string, error) {
    	if order.paid {
    		if order.locked {
    			return "", OrderLocked{orderId: order.id}
    		}
    		if len(order.items) < 1 {
    			return "", EmptyCart{orderId: order.id}
    		}
    		return order.items[0], nil
    	}
    	return "awaiting payment", nil
    }
    ```
  </Tab>
</Tabs>

`Unlocked` is `order.locked is False()`. `HasItems` is `order.items is Min(1)`.
If `order.paid` is false, `fulfill` returns `"awaiting payment"` and never
runs those checks. If it is paid, a locked order returns `OrderLocked` and an
empty cart returns `EmptyCart`. After both `ensure`s pass, `order.items[0]` is
safe. After the `if`, Forst does not treat the order as `Unlocked` or
`HasItems`.

## Keep guard bodies as checks

A guard body only checks the value in parentheses and any extra parameters.
The same input must always produce the same result.

You can use `if`, `else if`, `else`, `is`, and `ensure`. A guard cannot change
values, use `return`, or read unrelated variables.

If an `if` in a guard body does not match and has no `else`, the guard fails.
A guard also cannot return a named error with `else Unauthorized{}`. Put the
named error on the `ensure` that uses the guard:

```ft theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
error Unauthorized {}

ensure ctx is LoggedIn() else Unauthorized{}
```

Use `or` inside a guard when several conditions can satisfy the rule. See
[Accept several valid cases](/docs/language/check-a-value#accept-several-valid-cases).

## Add fields to a request shape

API procedures often share one context (who is calling) and differ on input
(what they sent). You write that as one request object with a `ctx` field and
an `input` field.

`AppContext` and `LoggedIn` are the ones from the start of this page. Every
procedure below already has `ctx: AppContext`. `createTask` adds a task name.
`getTask` adds a task id. Callers pass `ctx` and `input` together.

<Tabs>
  <Tab title="Forst">
    ```ft theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
    type MutationArg = Shape

    is (req MutationArg) Input(input Shape) {
    	ensure req is { input }
    }

    is (req MutationArg) Context(ctx Shape) {
    	ensure req is { ctx }
    }

    type AppMutation = MutationArg.Context(AppContext)

    func createTask(req AppMutation.Input({
    	input: { name: String },
    })) {
    	ensure req.ctx is LoggedIn()
    	return req.input.name
    }

    func getTask(req AppMutation.Input({
    	input: { taskId: String },
    })) {
    	ensure req.ctx is LoggedIn()
    	return req.input.taskId
    }

    sessionId := "479569ae-cbf0-471e-b849-38a698e0cb69"
    createTask({
    	ctx: {
    		sessionId: &sessionId,
    		user: { name: "Alice" },
    	},
    	input: {
    		name: "Fix the leak",
    	},
    })
    ```
  </Tab>

  <Tab title="Generated Go">
    ```go theme={"theme":{"light":"github-light-default","dark":"dark-plus"}}
    // forst build
    import errors "errors"

    type AppContext struct {
    	sessionId *string
    	user      *User
    }

    type AppMutation struct {
    	ctx AppContext
    }

    type MutationArg struct {
    }

    type T_488eVThFocF struct {
    	ctx   AppContext
    	input User
    }

    type T_CQWzvwMi9mY struct {
    	ctx   AppContext
    	input T_GtnrMUZwSZc
    }

    type T_GtnrMUZwSZc struct {
    	taskId string
    }

    type T_PBoS2ej5ec7 string

    type User struct {
    	name string
    }

    func G_HRerS5U4F3k(ctx AppContext) bool {
    	if ctx.sessionId == nil {
    		return false
    	}
    	if ctx.user == nil {
    		return false
    	}
    	return true
    }

    func G_cKNsLmdwv5n(req MutationArg, input T_PBoS2ej5ec7) bool {
    	return true
    }

    func G_gHGun6hNdbd(req MutationArg, ctx T_PBoS2ej5ec7) bool {
    	return true
    }

    func createTask(req T_488eVThFocF) (string, error) {
    	if !G_HRerS5U4F3k(req.ctx) {
    		return "", errors.New("ensure req.ctx is AppContext.LoggedIn(): want AppContext.LoggedIn()")
    	}
    	return req.input.name, nil
    }

    func getTask(req T_CQWzvwMi9mY) (string, error) {
    	if !G_HRerS5U4F3k(req.ctx) {
    		return "", errors.New("ensure req.ctx is AppContext.LoggedIn(): want AppContext.LoggedIn()")
    	}
    	return req.input.taskId, nil
    }

    func main() {
    	sessionId := "479569ae-cbf0-471e-b849-38a698e0cb69"
    	createTask(T_488eVThFocF{ctx: AppContext{sessionId: &sessionId, user: &User{}}, input: User{name: "Fix the leak"}})
    }
    ```
  </Tab>
</Tabs>

`type AppMutation = MutationArg.Context(AppContext)` means every procedure
already has `ctx`. `.Input({ input: { name: String } })` adds this procedure's
payload.

The call passes a session, a user, and a name. Inside `createTask`,
`ensure req.ctx is LoggedIn()` reuses the `LoggedIn` guard. After that check,
`req.input.name` is the string the caller sent, and `req.ctx.sessionId` is
present.

`getTask` is the same pattern with a different `input`. You do not repeat the
context field on each procedure.

### How Input and Context add fields

`MutationArg` is a request you can add fields to. In the example above, the
`Input` and `Context` guards require a field to exist and give it a type. The
names in parentheses (`input`, `ctx`) are those field types, not values at
runtime.

`ensure req is { input }` means the request must have an `input` field of the
type passed into the guard. `AppMutation.Input({ input: { name: String } })` is
that guard applied to `createTask`.

Request shapes that have no Forst name become hashed Go types in the Generated
Go tab. `Input` and `Context` compile to helpers that return `true`; the type
checker already required those fields. A failed `LoggedIn` check returns
`errors.New("ensure req.ctx is AppContext.LoggedIn(): want AppContext.LoggedIn()")`.

## Caveats

Type guards are experimental. The examples above parse, type-check, and
compile to Go. See the [roadmap](/docs/resources/roadmap).

A guard checks the value once at an `ensure` or `if`. Later changes do not run
the guard again. A write to a field the check used drops the fact. See
[When a later write drops the fact](/docs/language/check-a-value#when-a-later-write-drops-the-fact)
for field writes, pointer fields, and concurrency.

**Narrowing** after a check on a simple name (`ensure ctx is LoggedIn()`) is the
reliable case. A check on a dotted path (`ensure req.ctx is LoggedIn()`) still
has gaps. Editor hover may show the guard while later lines still see the
original type.

Narrowing inside an `if` branch may end with that branch. Code after the full
chain can see the earlier type.

A **shape guard** such as `Input` verifies that a field exists with the required
shape. It never creates or fills that field.

Generated Go uses functions or inline conditions for guard checks. An `ensure`
that uses the guard follows the usual Go error return path.

See [Check a value](/docs/language/check-a-value#caveats) for general
narrowing limits.

## Examples

See the type guard examples on GitHub:

* [`examples/in/rfc/guard/shape_guard.ft`](https://github.com/forst-lang/forst/blob/main/examples/in/rfc/guard/shape_guard.ft)
* [`examples/in/rfc/guard/basic_guard.ft`](https://github.com/forst-lang/forst/blob/main/examples/in/rfc/guard/basic_guard.ft)
* [`examples/in/rfc/guard/`](https://github.com/forst-lang/forst/tree/main/examples/in/rfc/guard/)

## Related

Continue with built-in constraints or apply guards with `ensure`.

<CardGroup cols={2}>
  <Card title="Describe a shape" icon="table-columns" href="/docs/language/shapes-and-constraints">
    Built-in constraints on primitive fields.
  </Card>

  <Card title="Check a value" icon="filter" href="/docs/language/check-a-value">
    Using ensure … is … at call sites.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.