Language Foundations

Types

Understand inferred and explicit types, then select unions, callable types, or nominal data for required boundaries.

Types allow the checker to answer a practical question before program execution. Which values may cross a given boundary? Most local values do not require an explicit type annotation. Add one when it clarifies an important input, return value, or domain choice.

Start with one checked program

Save this file as types.tpz:

TOPAZ
type Stage = "draft" | "ready"

function describe(stage: Stage, checks: int) -> string {
    "{stage} after {checks} checks"
}

let checks = 3
let current: Stage = "ready"
let render: (Stage, int) -> string = describe

print(render(current, checks))

Check the boundaries, then run the program:

BASH
topaz check types.tpz
topaz run types.tpz

The result is:

Output
ready after 3 checks

Read the types from the result

checks lacks a type annotation. Because its initializer is an integer, Topaz infers int. current includes an annotation because the program requires a Stage rather than an arbitrary string.

Stage is a transparent alias for the literal union "draft" | "ready". This union specifies that only the listed values are valid and rejects all other strings. The alias provides a clear name for this choice without creating a new runtime identity.

render demonstrates a callable type. (Stage, int) -> string specifies a function that accepts a Stage and an int, returning a string. Because the named function describe matches this signature, it can be assigned to the binding and invoked later.

Primitive type names are lowercase: int, float, bool, string, and the unit type (). Common containers use named constructors such as Array<T>, Map<K, V>, and Set<T>. Expected absence and recoverable failure are represented by Option<T> and Result<T, E>.

Choose the shape that states your intent

NeedPreferWhy
An obvious local valueinferenceThe initializer already conveys the type.
A public input, result, or important local boundaryan annotationThe checker and reader directly see the intended contract.
One value from a closed set of alternativesa union or literal-union aliasEach admitted alternative remains explicit.
A value that can be called latera callable type such as (int) -> boolParameter and return types stay attached to the value.
Domain identity, fields, or named casesa record, enum, or newtypeNominal declarations remain distinct even when their contents look alike.

A type alias improves domain vocabulary while remaining transparent. Choose nominal data when mixing two structurally similar values would be an actual error. The Records & Nominal Data page compares record, enum, and newtype construction and matching.

Give empty values enough context

Some values do not carry sufficient type information on their own. An empty array cannot determine its element type, and None cannot reveal the missing value type. Supply context through an annotation, a function parameter, a declared result, or another surrounding type:

let names: Array<string> = []
let selected: Option<int> = None

These canonical shapes are presented as text here. The runnable example above remains the page’s verified program.

Common mistake: writing the target language’s type spelling

Topaz collection types use Array<T>, Map<K, V>, and Set<T>. Callable types enclose parameter types in parentheses, followed by -> and the result type. Keeping these spellings explicit ensures that checker diagnostics and generated targets agree on the same source contract.

Exact limits

  • There is no implicit conversion between int and float.
  • Generic type parameters are invariant. Two distinct arguments do not become compatible merely because their contents seem related.
  • Generic bounds belong only to named function type parameters. Functions & Generics explains the admitted rank-1 form.
  • Recursive aliases, variance annotations, higher-rank polymorphism, and broader reflection remain deferred.
  • A generic empty value that still has unsolved type parameters is a static error rather than a runtime guess.

If inference, annotations, and mutation are still new, revisit Values and Functions. For choosing among absence, recoverable failure, and faults, continue to Null, Option, Result & Faults.