ensure keyword. It allows you to declare what you expect to be true.
If it is not, the function returns
an error (or exits if you’re inside main). If the condition holds, Forst keeps that knowledge for the lines
below, so you do not have to check again.
Stop unless a value is valid
Thisgreet function must not run with an empty name.
- Forst
- Generated Go
name is empty, greet returns EmptyName. return "hello, " + name runs
only after the check passes.
is Min(1) is the condition. else EmptyName{} is the error to return when it
fails. See Return a named error for how to declare
errors with fields.
Use the value after the check
After a successful check, Forst remembers that the condition held and narrows your variable. Later lines can usename without repeating the bound.
Describe a shape lists built-in
conditions such as LessThan, Min, Present, and Ok.
Handle multiple outcomes in the same function
Useif when both outcomes still belong in the function. Use ensure
when the branch you’re on 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:
- Forst
- Generated Go
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 ensures pass, order.items[0] is
safe.
But facts derived from ensure statements don’t survive when you exit the branch. So after the if, Forst does not treat the order as unlocked or as having
items.
For reusable names for those rules, see
Name a reusable rule.
Write a shorter check
Forst also offers shorthand forms to help with typical use cases. Take Go’sfilepath.IsAbs which returns a bool. You can check that result with ensure and
leave out is True():
- Forst
- Generated Go
openFile returns an error and never opens the file.
That keeps the open from depending on the process working directory.
ensure filepath.IsAbs(path) is the same as
ensure filepath.IsAbs(path) is True().
The check is the bool that filepath.IsAbs returned. Forst does not remember
that path is absolute. Later lines still treat path as an ordinary
String. If you want Forst to remember that path is absolute, put the Go
call in a type guard on path.
The same idea works for pointers, errors, and Result values. You write the
value. Forst fills in the usual check.
Continue only when a pointer is empty
This code stores a value inslot only when nothing is there yet.
- Forst
- Generated Go
ensure !slot is the same as ensure slot is Nil(). If slot already holds a
pointer, claim returns SlotAlreadyTaken and does not overwrite it.
open waits until claim succeeds. ensure claim(7) is the same as
ensure claim(7) is Ok(). If claim fails, open returns that same
SlotAlreadyTaken error. claim has no success value. Checking the call is
enough.
ensure slot (no !) means the pointer is non-nil, the same as
ensure slot is Present(). On a map or array, Present and Nil mean
nilness, not emptiness. An empty map or [] still passes Present. Use
is NotEmpty() or is Min(n) for length.
Use the value from a Result
Map lookups and fallible calls returnResult. Check success before you use
the value. After placeOrder returns a Result, this continues only on
success:
- Forst
- Generated Go
ensure order is the same as ensure order is Ok(). On failure, the function
that contains this ensure returns order’s error (except in main and Go
tests). After the check, order is the successful value.
The same pattern works when you need that value in the next check. Here in is
the order request and catalog maps SKUs to quantities:
- Forst
- Generated Go
ensure avail, avail is the quantity in the map. The next line can
compare in.quantity with it. If the lookup failed, the handler returns the
lookup error. If the quantity is too high, it returns InsufficientStock with
the SKU, the request, and what was available.
When a Go call returns err, write ensure !err. That is the same as
ensure err is Nil(), and a failed check returns that error. See
Functions that can fail.
Short forms
When the type is known, bareensure and ensure ! expand to these checks.
else stays optional in every case.
Other values still need an explicit
is condition, as name did in greet.
Accept several valid cases
Useor to join several acceptable checks on the same value. This status
continues if it is either pending or processing:
advance returns InvalidStatus.
Write the value once. Follow it with is Pending(), then or Processing().
Chained constraints stay together before or. In
ensure password is String.Min(3).Max(32) or UUIDV4(), the first option checks
a length range. The second runs UUIDV4().
Inside a type guard, ensure cannot return a custom
error with else. Use or so any of several conditions can satisfy the guard.
Callers then attach the error at the call.
Check against a named type
Write the type name without parentheses afteris to check membership in that
type. This works for literal unions and for constrained aliases that use a
dotted chain with no or alternatives.
type HttpPort = Int.Min(1).Max(65535) and similar aliases.
Omit parentheses for named types. Use parentheses for
guards and assertion functions (Strong(), Pending()).
For closed lists of allowed values, see
List alternatives.
Exit from main
main starts the process. A failed ensure there ends the process with exit
status 1. There is no caller to return an error to, so a named error after
else is not allowed.
Write ensure drive(80) when you only need that exit. Add else { ... } when
you want to log or clean up first. The block runs on failure. The process still
exits afterward.
This example calls drive, then stop. The first check has no block. The
second prints a line if stop fails.
- Forst
- Generated Go
drive fails, the program writes to stderr and exits. stop never runs.
If stop fails, the program prints failed to stop, then exits. Nothing after
that ensure in main runs.
When a later write drops the fact
A successfulensure establishes a fact about a variable or field. Forst
remembers that fact on later lines, so you do not re-validate before a call
that requires it.
ensure order is Shippable() else CannotShip{}, ship(order) runs
without checking order.address.country again. Forst already knows order
satisfied Shippable().
What drops the fact
A write to a field the check depends on drops the fact.Shippable because the country changed. Forst
reports the problem at ship(order) with:
- the dropped condition name (
Shippable) - the line where it was established
- the write that invalidated it
- how to restore it (
ensure order is Shippable())
ensure order is Shippable() again after changing dependent fields.
A write to an unrelated field leaves the fact intact.
lastViewedAt is not part of Shippable, so ship(order) is still valid.
Reading a field never drops a fact.
Assigning nil to a pointer field (order.address = nil) drops presence facts
on address and its child fields. Writing through an existing pointer
(order.address.country = "US") changes the pointed-to value and does not
clear presence on address.
Caveats
See the roadmap for work that is not finished yet. A fact survives anif / else split only when it holds on every path. Loops
assume zero iterations may run, so facts from a loop body do not apply after
the loop unless they are true even when the loop never runs.
Calling a Forst function that mutates a field the check used drops that fact.
Calls to Go packages, unsafe, or reflect drop facts on mutable arguments
the callee can reach. The call itself remains legal.
Writing through a pointer alias (alias := ℴ alias.address.country = nil)
drops facts on the aliased value. Starting a goroutine (go worker(order)) or
sending data that contains pointers through a channel clears facts on shared
mutable storage.
Forst map lookups return a Result. Use ensure avail before reading the
value.
Generated Go uses ordinary conditionals and error returns for ensure. A
custom failure after else becomes a named Go error type. In main, a failed
check exits the process instead of returning.
Examples
See the ensure and narrowing examples on GitHub:examples/in/ensure.ftexamples/in/ensure_bool.ftexamples/in/ensure_else_method.ftexamples/in/result_ensure.ftexamples/in/result_void_ensure.ftexamples/in/result_unwrap_propagate.ftexamples/in/result_if.ft
Related
Continue with named errors or reusable conditions.Return a named error
Give each expected failure a name and fields.
Name a reusable rule
User-defined predicates for domain rules.
Functions that can fail
Result returns and Ok / Err checks.
Describe a shape
Built-in constraints on primitive fields.