Skip to main content
Sometimes one value can have several valid forms. In other cases, a value must meet several requirements at once. Forst uses unions for alternatives and intersections for combined requirements. The supported union forms are named error unions and named homogeneous literal unions (string, integer, or boolean literals). Other general unions and intersections remain experimental.

List the possible string or numeric values of a field

Use a literal union when a field can contain only a specific set of primitive values. The | operator separates each literal value.
Use ensure with the bare type name to validate untrusted input at runtime:
After ensure raw is TaskStatus, raw has the narrowed TaskStatus type. See Check a value.

Subset types

A smaller literal union is assignable to a larger literal union that contains all of its values.
PendingStatus is assignable to TaskStatus because every value in PendingStatus belongs to TaskStatus. The reverse assignment (TaskStatus to PendingStatus) is rejected unless you validate the value first with ensure.

Exhaustive switches

When a switch statement handles a literal union, the compiler checks that every possible value is covered.
If a case is missing and there is no default clause, the compiler reports a non-exhaustive switch diagnostic. Unreachable default cases when all values are handled are also reported.

List the errors a function can return

Use an error union when a function has a small set of expected failures.
LoadFailed accepts ParseFailed or IoFailed. Each error keeps its own fields. The Result tells callers that load returns an integer or one of these two failures. See Return a named error and Functions that can fail for how to create and return the errors.

Handle one error from the list

Use Err(ErrorName) when each error needs a different response.
Inside the branch, result is a ParseFailed. Generated Go uses an interface that only the listed errors implement. Generated TypeScript uses the same alternatives.

Accept several kinds of value

A general union can describe a value with several possible object or primitive types.
Forst can parse and check this named declaration. Generated Go currently uses any for a general union, so type detail is lost after generation. Avoid general unions in application contracts until general Go output and narrowing are complete.

Require several sets of fields

An intersection uses &. It describes a value that must satisfy every member.
NamedEntity requires both name and id. Intersection parsing and type checking exist, while complete narrowing and generated Go types remain in development. Treat intersections as experimental.

Choose the type for the job

Start from the behavior your function needs. Result and Tuple have different guarantees. See why they stay separate before wrapping calls with several return values.

Current limits

  • Literal unions must be homogeneous (all string, all integer, or all boolean literals). Mixed, float, inline, shape, tagged, and general disjunctive unions remain experimental or planned.
  • Union and intersection syntax works in named type declarations (type T = ...).
  • Inline forms such as a parameter typed directly as "a" | "b" are unavailable; declare a named type.
  • Named error unions and named homogeneous literal unions are the supported end-to-end union paths.
  • Arbitrary type unions (String | Int) lose detail in generated Go.
  • Optional types such as T | Nil are still planned.
See the roadmap for current implementation status.

Examples

See the tested error union and literal union examples on GitHub: Continue with typed failures or runtime narrowing.

Return a named error

Return and handle named failures.

Check a value

Refine values after runtime checks.

Functions that can fail

Result returns and Ok / Err checks.