# Topaz v5.20 documentation
> Complete English manual in agent-readable Markdown.
> Use https://topaz.ooo/llms.txt when a compact index is more appropriate.
>
> This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
---
route: https://topaz.ooo/v5.20/en/docs/introduction
markdown: https://topaz.ooo/docs/introduction.md
covers: §0, §1, §2, §3, §4, §5, §6, §7, §12, §13, §13a, §14, §15, §16, §17, §20, §22
# Introducing Topaz
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Topaz is a compact language for application intent. It reads like Python or TypeScript but prefers a single, consistent way to express each common idea. The same rules apply whether code is written by a developer or generated by a tool.
The source of Topaz 5.20 is public at topaz.ooo/dl.
Topaz works one layer above the machine, on command-line tools, data transformation, service logic, configuration, and the orchestration of SQL, shell and paths. You can run the same source directly or build a native program, Python package, or web artifact.
This page explains where the language fits and how its code feels. If you are new to Topaz, follow the [Learning path](/en/docs/learn) or run your first program in the [Playground](/playground). Use [Syntax at a Glance](/en/docs/language-tour) for a concise overview of the language, and read the [language philosophy](/en/docs/philosophy) to understand its design choices.
Every example on this page runs through the Topaz toolchain, and the output under each one is what it printed.
## What Topaz is good at
### Four products, one source
`topaz run` executes the file you just wrote. `topaz build` turns that same file into a self-contained native binary, into `program.py` beside its runtime support file `topaz_py_rt.py`, or into a web package for a browser host. Moving a program to a new deployment is a flag on one command.
### One settled form
Every ordinary idea has one canonical form and the language holds that line. Recoverable failure goes through `Result`, expected absence goes through `Option`, and cleanup goes through `defer`. No file in a Topaz codebase drifts into a dialect of its own.
### Failure has a shape
Failure is part of the type, so a reader meets it in the signature. The postfix `?` carries an `Err` to the caller and `defer` runs on every exit path, including the one that failed. `match` is checked for missing cases.
### Your words are names
Identifiers are Unicode from the lexer level upward and no normalization pass runs behind them. Two names are equal when you typed the same characters. The module resolver rejects identifiers that invite confusion, so your domain keeps its own vocabulary.
### Small on purpose
The surface stays small because the small surface is the product. The same compact grammar is what a developer reads and what a code generator writes. A new form arrives with defined behavior, a working implementation, examples and documentation.
## What Topaz code feels like
### Unicode-first
Identifiers support Unicode from the lexer level upward. Domain terms remain in your native language rather than becoming romanized approximations.
```topaz
function greet(name: string, language: string) -> string {
return match language {
case "한국어" => "안녕하세요, {name}님!"
case "Русский" => "Привет, {name}!"
case _ => "Hello, {name}!"
}
}
let 사용자 = "김토파즈"
print(greet(사용자, "한국어"))
print(greet("Topaz", "Русский"))
```
```text output
안녕하세요, 김토파즈님!
Привет, Topaz!
```
`사용자` is a standard variable. Unicode identifiers are a normal part of the language. Topaz does not silently rewrite entered names, and the module resolver rejects identifiers that cause confusion.
### A coherent canonical surface
Common choices have a canonical form. Recoverable failures use `Result`, expected absence uses `Option`, resource cleanup uses `defer`, and `match` evaluates whether all cases are covered. The language fixes these choices so that no file diverges into its own dialect.
```topaz
function parsePort(raw: string) -> Result {
let n = toInt(raw) ?? -1
if n < 1 {
return Err("not a port: {raw}")
}
return Ok(n)
}
function startup(raw: string) -> Result {
defer print("config closed")
let port = parsePort(raw)?
return Ok("listening on {port}")
}
print("{startup("8080")}")
print("{startup("http")}")
```
```text output
config closed
Ok(listening on 8080)
config closed
Err(not a port: http)
```
The `?` operator propagates an `Err` to the caller, while `defer` executes on every exit path. That is why `config closed` appears before both results.
### Templates keep intent structured
The template registry includes `p`, `r`, `sh`, and `sql`. For example, `p"..."` is a path template, while `sql"..."` keeps SQL text separate from interpolated parameters instead of collapsing everything into a single string.
```topaz
let table = "users"
let q = sql"select * from {table} where active = {true}"
print("{q}")
```
```text output
```
The output displays a structural summary. The template retains the distinction between literal text fragments and interpolated values.
### Check once, choose an output
Topaz is statically typed. The checker validates the complete program before execution.
```topaz
type TrafficLight = "red" | "yellow" | "green"
function next(light: TrafficLight) -> TrafficLight {
return match light {
case "red" => "green"
case "green" => "yellow"
case _ => "red"
}
}
```
`topaz run` parses, resolves, checks, and executes the program. From that same source, `topaz emit` and `topaz build` create alternative outputs. The Rust target produces a self-contained native binary. The Python target writes `program.py` alongside its support file `topaz_py_rt.py`.
Rust and Python outputs are evaluated with shared language scenarios. [Toolchain Status](/en/docs/toolchain/status) describes target-specific behavior and selection guidance.
```bash
topaz build app.tpz --target python --out-dir py-out
```
## When not to use Topaz
Topaz focuses on managed application workflows. Rust, C, or Zig fit work that requires direct ownership and lifetime control, unsafe memory boundaries, embedded targets, manual performance tuning, or systems-level control.
Topaz targets high-level application workflows: deployable application logic without a systems-level source surface, domain language expressed in code, and a minimal grammar that developers and tools can produce consistently. When that describes your project, proceed with the [Learning path](/en/docs/learn).
**One way to say it.**
---
route: https://topaz.ooo/v5.20/en/docs/learn
markdown: https://topaz.ooo/docs/learn.md
covers: §0
# Learning path
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
**Outcome:** Select the appropriate starting environment and follow the step-by-step progression from a single `.tpz` file to a tested two-module product.
**Prerequisites:** You only need a text editor and basic familiarity with the terminal. The final lesson requires Python 3.11 or newer.
## Choose your starting point
Use the [Playground](/playground) to modify and run short code snippets without installing local dependencies. It covers the first four lessons, but it cannot generate the package or artifact built in the final lesson.
Install Topaz locally to complete the entire learning path. [First Program](/en/docs/getting-started) provides installation options for standard shells, PowerShell, and npm, followed by consistent `topaz` CLI commands across all platforms.
Most readers complete the five hands-on lessons in about an hour. You can pause at any readiness check and resume later. Each lesson leaves the study-plan application in a working state.
If you already program in another language, start with [Coming From Other Languages](/en/docs/learn/coming-from-other-languages). It shows what transfers unchanged and what does not, so you can take only the lessons you need.
## What you will build
The application evolves incrementally across lessons:
1. **[First Program](/en/docs/getting-started)** — Write a study-plan value, run `check` and `run`, and resolve a static type diagnostic.
2. **[Values and Functions](/en/docs/learn/values-functions)** — Add explicit and inferred types, introduce a function, and manage the single mutable variable required by the application.
3. **[Data and Control](/en/docs/learn/data-control)** — Model study tasks using nominal data types, store them in a collection, and control execution using `if`, `match`, and `for`.
4. **[Failures and Resources](/en/docs/learn/failures-resources)** — Process invalid input using `Option`, `Result`, and `?`, ensure cleanup with `defer`, and inspect an intentional error condition.
5. **[First Application](/en/docs/learn/first-application)** — Modularize the codebase into `src/main.tpz` and `src/plan.tpz`, add a test suite, lock and verify package dependencies, and build a Python product.
The final command executes the generated artifact from a directory containing no `.tpz` files, operating independently of the Topaz project structure and compiler. Topics such as package registries, browser lifecycle management, HTTP hosting, and backend architecture are optional next steps rather than initial prerequisites.
## Decision: Playground or local tools?
Choose the Playground to evaluate expressions quickly in the browser. Choose a local installation to build the tested application and retain its output artifact. You can begin in the browser and transition to a local environment before the final lesson without losing context.
## Try this
Decide where you will complete the course. Do you require a local installation now?
### Answer
Use the Playground for an initial syntax overview. Install Topaz locally before the First Application lesson, as it generates source files, executes test suites, and builds a Python distribution package. If you plan to complete the entire guide in one session, starting locally avoids switching tools midway.
## Ready to continue when
You have selected your starting environment, understand that the target output is a tested two-module Python product, and have either opened the Playground or prepared your environment for installing `topaz`.
Continue to [First Program](/en/docs/getting-started). Keep [Syntax at a Glance](/en/docs/language-tour) open as a quick reference whenever you need to review language constructs.
---
route: https://topaz.ooo/v5.20/en/docs/learn/coming-from-other-languages
markdown: https://topaz.ooo/docs/learn/coming-from-other-languages.md
covers: §3, §4, §7, §8, §12, §13
# Coming From Other Languages
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
**Outcome:** Translate your existing programming habits into the patterns used here, and identify which patterns are intentionally omitted.
**Prerequisite:** Ability to read code in at least one other language. No prior experience with this language is required.
## Start from what transfers unchanged
Much of the syntax will look familiar. Values, names, functions, conditionals, loops, and collections perform recognizable roles, while the checker explicitly highlights key differences. You should be able to understand what the code snippet below does before learning every detail.
```topaz
record Task {
title: string,
minutes: int
}
function totalMinutes(tasks: Array) -> int {
let mut total = 0
for task in tasks {
total = total + task.minutes
}
total
}
```
If you have written Python, JavaScript, Java, or Rust, you can already determine what this code does. That is the rationale for starting here. The key differences worth noting are few, as outlined below.
## Four differences that actually matter
**Mutation is opt-in.** A plain `let` binding cannot be reassigned. Declaring `let mut` specifies that reassignment is intended. Languages where variables are mutable by default often require examining more of a function to determine which names change. Here, the declaration answers that question.
**Recoverable failure is a value.** There are no exceptions to catch. A function with a recoverable failure returns a `Result`, which the caller either handles or propagates using `?`. The call boundary carries the failure structure instead of requiring you to infer it from a function body or exception convention. Runtime faults remain a separate, non-recoverable concern.
**Absence is represented in the type.** Standard optional values use `Option`. External nullable data uses `T | null`, keeping the two models distinct. In both cases, the checker requires handling the absent case before treating the value as present.
**The selected profile defines host authority.** File and I/O operations use the selected host or product profile, and web products receive the capabilities declared by that profile. A function signature indicates data and recoverable failure. Imports, package configuration, and the selected profile complete the authority scope. [Reading a Real File](/en/docs/learn/reading-topaz) uses all of these mechanisms.
## What is deliberately absent
Understanding which patterns are omitted helps avoid reaching for unmapped habits early on.
There are no class hierarchies or inheritance structures. Data shapes are records, and behavior lives in functions operating on them. If your instinct is to design with base classes and overrides, model with records and explicit functions instead.
There is no runtime reflection over your program, no dynamic dispatch via protocol tables, and no row polymorphism. Details on deferred capabilities are in [Records & Nominal Data](/en/docs/language/records-nominal-data).
Implicit conversions between numeric types are not supported. An `int` does not convert to a `float` for convenience in surrounding expressions.
These design boundaries are intentional. They keep critical choices visible in code declarations and configuration without assuming a function signature captures every host interaction.
## A translation table for the first hour
A `dict`, `Map`, or `HashMap` maps to a collection, detailed in [Collections & Comprehensions](/en/docs/language/collections-comprehensions). A data-only `class` often maps to a record. Define interface boundaries using explicit function signatures. Use a static protocol when a generic contract requires one, without runtime dynamic dispatch. Recoverable `try`/`except` logic maps to returning a `Result`. Standard optional results use `Option`. External nullable data uses `T | null`, with the checker maintaining an explicit distinction between them.
## Try this
Consider a function that looks up a task by title and might not find one. Before reading the answer, decide what its return type should be.
### Answer
An `Option` of a task, rather than a task type alone. A missing item is an ordinary outcome of a lookup operation rather than an error, so it is expressed in the type as absence. A `Result` would be appropriate if the lookup operation itself could fail, such as when reading from a file. Distinguishing between absence and failure is an explicit choice required by this language, and defining it in the signature saves every caller from guessing.
## Ready to continue when
You can list the four key differences, identify which habits from previous languages are omitted here, and choose between absence and failure for a function you are about to write.
Work through the course starting at [First Program](/en/docs/getting-started) if you have not done so, or move directly to [Syntax at a Glance](/en/docs/language-tour).
---
route: https://topaz.ooo/v5.20/en/docs/learn/what-a-program-is
markdown: https://topaz.ooo/docs/learn/what-a-program-is.md
covers: §3, §4, §7, §16
# What a Program Is
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
**Outcome:** Read a short program and explain how each line contributes to the observed output.
**Prerequisite:** None. This guide assumes no prior programming experience. Experienced developers can skip directly to [First Program](/en/docs/getting-started).
## A program is a description, not an instruction shouted at a machine
Programming is often perceived as commanding a computer step by step. In practice, it resembles writing a precise specification that eliminates ambiguity. You define entities such as values and functions, and specify how results are derived from those inputs. The runtime engine does not guess what outcome you intended. It executes this written specification literally, including any errors in logic.
That final point defines software development. An execution engine cannot infer developer intent or correct mistaken assumptions automatically. Computers follow the given rules and definitions without applying human intuition. Learning a language involves expressing precise logic without unintended ambiguities, ensuring that the specification matches the intended goal.
## Four ideas cover most of it
Most programs are constructed from four core concepts. Understanding how these four elements work together allows you to read code by identifying what data exists and how it moves through the system.
A **value** represents data, such as the integer `45`, the string `"study plan"`, or the boolean `true`. Values are the concrete pieces of information that a program evaluates and displays. In the example code below, `"study plan"`, `45`, and `20` are values defined on lines 5, 6, and 7.
A **name** references a value for subsequent use. For example, writing `let total = 45` binds the identifier `total` to the value `45`. Naming values prevents repeating raw data throughout a file and makes the code readable. In lines 5 through 7, `plan`, `total`, and `spent` are names created to hold data for later calculation.
A **function** is a named unit of computation. It accepts input values and returns a result, allowing calculation logic to be defined once and reused across a codebase. Lines 1 through 3 define the `minutesLeft` function, which receives inputs and computes the difference `total - spent`.
A **run** is the execution of the program description to produce observable output. During a run, the system evaluates expressions line by line to produce final results. Line 9 executes during a run to combine the function output with strings and display the final text on the screen.
The following example combines all four concepts in a single file.
```topaz
function minutesLeft(total: int, spent: int) -> int {
total - spent
}
let plan = "study plan"
let total = 45
let spent = 20
print("{plan}: {minutesLeft(total, spent)} minutes left")
```
If Topaz is installed, save the code as `what-a-program-is.tpz`, then check and run it using the commands below. Otherwise, review the explanation for now. The [First Program](/en/docs/getting-started) section covers toolchain installation prior to writing custom files.
```bash
topaz check what-a-program-is.tpz
topaz run what-a-program-is.tpz
```
The command produces the following output:
```text output
study plan: 25 minutes left
```
## Read the result backwards into the code
Start from the output and trace it back to the source. The printed output `study plan: 25 minutes left` is constructed from separate pieces evaluated during execution. The string `study plan` originates from the variable `plan`, which references `"study plan"`. The numeric value `25` is not hardcoded inside the print statement; it is dynamically evaluated by `minutesLeft` from inputs `45` and `20`.
Look at the function definition again. The signature shows `total: int, spent: int` and `-> int`. The `: int` following a name indicates that an integer value is expected in that position, while `-> int` indicates that the function returns an integer. Detailed rules for type annotations are covered formally in the next lesson. Understanding their basic role is sufficient for now. The signature establishes its input and output contract, accepting two integer parameters `total` and `spent` and declaring an integer return type.
The body of the function consists of a single expression, `total - spent`, which evaluates to the final result of `25`. In Topaz, the evaluation of this body expression determines the return result. External context cannot alter function behavior, as all dependencies are passed explicitly through parameters.
This property provides isolated reasoning. A computation that depends solely on explicit inputs can be evaluated independently, without requiring knowledge of global program state. When reading or writing functions, examining the declared inputs and the body expression is sufficient to predict what the function will return.
## Why the check step exists
The workflow uses two commands to separate description analysis from execution. The check step analyzes the program description and identifies missing inputs before execution. If `minutesLeft(total)` is called without the second argument, the check reports the error location and halts execution before the run begins. This prevents the program from executing with missing arguments.
Some environments detect missing arguments only during execution when that line is reached, whereas statically checked languages identify them earlier. Topaz exposes this validation as an explicit command prior to execution, which is why `check` precedes `run` throughout these guides. Checking the code first gives immediate feedback on structural errors before running the application.
## Try this
Change `spent` to `50` while leaving other values unchanged. Predict the output prior to running the code.
### Answer
The output becomes `study plan: -5 minutes left`. The subtraction expression is evaluated literally as written, as the program description contains no constraint requiring positive results. Calculating `45 - 50` yields `-5` because arithmetic rules are applied without evaluating whether negative time makes practical sense. If negative values are invalid for domain logic, that requirement needs to be explicitly defined in code. The engine executes the written instructions rather than intended outcomes.
## Ready to continue when
You can identify a value, a name, a function signature, and its result expression, while explaining the operational difference between the check and run commands.
Install the toolchain and write a first program in [First Program](/en/docs/getting-started). Developers familiar with another language can review [Coming From Other Languages](/en/docs/learn/coming-from-other-languages) to map existing knowledge to these concepts.
---
route: https://topaz.ooo/v5.20/en/docs/getting-started
markdown: https://topaz.ooo/docs/getting-started.md
covers: §3, §4, §5, §16, §22
# First Program
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
**Outcome:** Create one `.tpz` file, check it before execution, run it, and repair an error identified by the checker's source span.
**Prerequisite:** Choose a starting point on the [Learning path](/en/docs/learn). Run the commands below in a local terminal.
## Install the command
On macOS or Linux:
```bash
curl -fsSL https://topaz.ooo/install.sh | sh
```
On Windows PowerShell:
```powershell
irm https://topaz.ooo/install.ps1 | iex
```
If Node is already installed:
```bash
npm install -g topaz-lang
```
Confirm the installed product:
```bash
topaz version
```
```text output
Topaz 5.20.2
```
This first program does not require a repository clone, package manifest, Rust, or Python.
If you use VS Code or an Open VSX-compatible editor, install the official Topaz extension from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=studiohaze.topaz) or the [Open VSX Registry](https://open-vsx.org/extension/studiohaze/topaz). It recognizes `.tpz` files, displays the Topaz file icon, and provides syntax highlighting. It also launches the installed `topaz lsp` to display editor diagnostics. The extension does not bundle a compiler, so ensure the installed Topaz executable is available on `PATH` or set `topaz.executablePath`. You can also run `topaz check` directly from the terminal.
## Write the smallest study plan
Create `first-program.tpz` with the following contents:
```topaz
let topic = "Topaz basics"
let minutes: int = 25
print("{topic}: {minutes} minutes")
```
Check the source file, then run it:
```bash
topaz check first-program.tpz
topaz run first-program.tpz
```
`check` validates the file without executing it:
```text output
first-program.tpz: types-ok (1 module)
first-program.tpz: resolve-ok (1 module)
```
`run` passes the same static gate and then prints:
```text output
Topaz basics: 25 minutes
```
Read the program in execution order. The first `let` binding assigns a string and lets the checker infer its type. The second binding explicitly specifies `int`, requiring the checker to verify that `25` is an integer. `print` evaluates the interpolated expressions inside `{...}` and outputs a single line.
## Read and repair a diagnostic
Create a separate file named `first-program-error.tpz`. This source code is intentionally invalid and is presented as plain text rather than a runnable Topaz example:
```text
let topic = "Topaz basics"
let minutes: int = "25"
print("{topic}: {minutes} minutes")
```
Run the checker:
```bash
topaz check first-program-error.tpz
```
The checker does not execute the file. It identifies the declared type, the type it found, and the exact source span:
```text output
error[TPZ5001]: expected `int`, found `string`
--> first-program-error.tpz:2:20
|
2 | let minutes: int = "25"
| ^^^^
first-program-error.tpz: 1 type diagnostic
```
Repair the highlighted value by changing `"25"` to `25`. Running `topaz check first-program-error.tpz` now succeeds, and running `topaz run first-program-error.tpz` produces the same output as the working program above.
## Decision: check or run?
Use `topaz check` while editing to verify source validity without starting the program. `topaz run` performs the same static check before executing the code to demonstrate behavior. Running `check` separately provides a fast, execution-free validation step rather than an additional check that `run` omits.
## Try this
Change `topic` to `"Functions"` and `minutes` to `30`. What should the program output?
### Answer
It outputs `Functions: 30 minutes`. Both edits preserve the inferred `string` and declared `int` types, allowing `topaz check first-program.tpz` to succeed before execution.
## Ready to continue when
You can create a `.tpz` file, execute `topaz check` and `topaz run`, and use the expected and found types alongside the highlighted source span to repair invalid values.
Continue to [Values and Functions](/en/docs/learn/values-functions). Refer to [Syntax at a Glance](/en/docs/language-tour) whenever you need a quick syntax reference.
---
route: https://topaz.ooo/v5.20/en/docs/learn/values-functions
markdown: https://topaz.ooo/docs/learn/values-functions.md
covers: §3, §4, §5, §7
# Values and Functions
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
**Outcome:** Connect runtime values to checked types and place a repeated calculation inside a named function.
**Prerequisite:** Complete [First Program](/en/docs/getting-started) and be able to run `topaz check` before `topaz run`.
## Extend the working file
Create `values-functions.tpz`:
```topaz
function minutesLeft(total: int, spent: int) -> int {
total - spent
}
let topic = "Topaz basics"
let total: int = 45
let spent = 20
let mut sessions = 0
sessions = sessions + 1
print("{topic}: {minutesLeft(total, spent)} minutes left")
print("sessions: {sessions}")
```
Check and run the program:
```bash
topaz check values-functions.tpz
topaz run values-functions.tpz
```
The expected output is:
```text output
Topaz basics: 25 minutes left
sessions: 1
```
## Read from the result back to the code
The previous lesson introduced type notations like `: int` and `-> int` briefly, promising to explain them in detail here. This section fulfills that promise by examining how Topaz handles types across function signatures and variable bindings.
The first line of output shows the result of calling `minutesLeft(45, 20)`. Looking at the function definition, `function minutesLeft(total: int, spent: int) -> int` uses `: int` after each parameter name to declare that `total` and `spent` expect integer values. The `-> int` notation after the parameter list specifies the function return type. These explicit type annotations communicate the function contract clearly to anyone reading or calling the function. Readers can see what inputs the function requires and what value it produces without inspecting the inner function body.
Inside `minutesLeft`, the final expression `total - spent` evaluates to an integer and yields that result without an explicit `return` statement. Topaz automatically returns the value of the last expression in a function block. Use `return` when an early exit improves control flow readability. The declared return type `-> int` applies to all potential exit paths.
In the variable declarations, Topaz relies on type inference. Type inference means Topaz automatically determines the type of a binding from its initial assigned value. `topic` and `spent` require no explicit type annotations because their initial values, `"Topaz basics"` and `20`, make their string and integer types clear. When the initial value is obvious, omitting the type annotation keeps the code concise.
By contrast, `total: int = 45` uses an explicit type annotation `: int`. Adding an explicit type annotation is helpful when defining an important type boundary or when you want to make the expected type explicit for documentation purposes. `topic`, `total`, and `spent` are all immutable bindings created with standard `let`. The program can read and use their values, but it cannot reassign them.
`sessions` works differently. The `let mut` declaration indicates that reassignment is intended for this binding. The line `sessions = sessions + 1` updates the stored value of `sessions` from `0` to `1`. Reserve mutation for bindings that track changing state, rather than marking every value as mutable simply because it might change later.
## Decision: infer, annotate, or mutate?
Choosing whether to infer types, write explicit annotations, or mark bindings mutable depends on how each value is used in your program.
Use standard `let` with type inference for local values with obvious types. When an initial value makes the type clear, omitting the annotation reduces clutter while keeping the code easy to read.
Add an explicit type annotation when defining an important boundary in your code. Explicit annotations like `total: int` make type expectations clear at a glance for anyone reviewing or maintaining the program.
Keep function signatures explicit with parameter types like `(total: int, spent: int)` and return types like `-> int`. Callers depend on these parameter and return type contracts to know how to interact with the function without needing to read through its implementation details.
Add `mut` only when subsequent reassignment is required by the design. Immutable bindings are easier to reason about because their values remain constant after initialization.
## Try this
Change `total` to `60` and `spent` to `15`. Leave `sessions` unchanged. How does the output change?
### Answer
The first line becomes `Topaz basics: 45 minutes left`. The second line remains `sessions: 1`. The function receives the updated arguments `60` and `15` to calculate `60 - 15`, while the separate mutable counter still increments once from `0` to `1`.
## Ready to continue when
You can explain type inference, an explicit annotation, immutable bindings, mutable bindings, function parameters, and the expression that produces the function return value.
For specific syntax, see [Types](/en/docs/language/types) and [Functions & Generics](/en/docs/language/functions-generics). Generics serve as reference material for later sections and are not a prerequisite. Proceed to [Data and Control](/en/docs/learn/data-control).
---
route: https://topaz.ooo/v5.20/en/docs/learn/data-control
markdown: https://topaz.ooo/docs/learn/data-control.md
covers: §5, §6, §8, §9, §10
# Data and Control
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
**Outcome:** Model a well-defined domain using a record and a collection, then select `if`, `match`, and `for` based on the specific question each control structure answers.
**Prerequisite:** Complete [Values and Functions](/en/docs/learn/values-functions) and understand bindings, type annotations, parameters, and function return values.
## Give each task a shape
Create `data-control.tpz`:
```topaz
record StudyTask {
title: string,
minutes: int,
done: bool = false,
}
function pace(task: StudyTask) -> string {
if task.done {
return "done"
}
match task.minutes {
case 1..15 => "quick"
case 16..30 => "focused"
case _ => "deep"
}
}
let tasks: Array = [
StudyTask { title: "Run first program", minutes: 10, done: true },
StudyTask { title: "Build application", minutes: 25 },
]
let mut remaining = 0
for task in tasks {
if !task.done {
remaining = remaining + task.minutes
}
print("{task.title}: {pace(task)}")
}
print("remaining: {remaining} minutes")
```
Run the standard two-step workflow:
```bash
topaz check data-control.tpz
topaz run data-control.tpz
```
It prints:
```text output
Run first program: done
Build application: focused
remaining: 25 minutes
```
## Follow the three decisions
A `record` is a named data structure that groups related fields into a single nominal type. Defining a record provides explicit names and types for each property instead of relying on loose values or unlabelled positional tuples. This choice keeps domain attributes bundled together under a clear name. In the example code, `record StudyTask` defines this shape with `title`, `minutes`, and `done` fields, where `done` defaults to `false` when omitted during construction.
An `Array` is an ordered collection that holds multiple elements of the same type in a sequence. An array is chosen here because tasks have a specific order in a study plan, and keeping duplicate or sequential items intact is important for processing them in order. Alternative collection structures focus on key lookup or uniqueness rather than ordered traversal. In the example code, `let tasks: Array = [ ... ]` creates an ordered sequence holding two `StudyTask` record instances.
While `record` and `Array` establish the shape and organization of data, control flow structures direct how that data is processed. The constructs `if`, `match`, and `for` handle distinct execution decisions to evaluate data elements.
A `for` loop is a control structure that iterates over each item in a collection sequentially. A `for` loop is chosen here because every task in the array needs to be visited once in order to compute total minutes and display results. Other constructs would require managing index positions manually or checking conditions repeatedly without directly tracking the collection layout. In the example code, `for task in tasks { ... }` binds each element of `tasks` to the variable `task` during each step of the iteration.
An `if` statement is a control structure that evaluates a single boolean expression to decide whether to execute a block of code. An `if` statement is chosen here because the decision depends on a binary true or false condition rather than multi-way branching or value matching. Using `if` keeps simple conditional checks clear without unnecessary syntax. In the example code, `if !task.done` inside the loop checks whether a task is unfinished before adding its minutes to `remaining`, while `if task.done` inside `pace` performs an early return for completed tasks.
A `match` expression is a control structure that compares a value against pattern cases and produces a result from the matching arm. A `match` expression is chosen here because classifying numeric ranges into distinct category labels requires choosing among multiple possible outcomes. Using `match` handles multi-case classification directly instead of chaining multiple boolean conditional branches together. In the example code, `match task.minutes` evaluates `task.minutes` against range patterns and wildcards to return a descriptive pace string.
Checking whether a `match` expression handles every possible value pattern is a key language behavior. When every case is covered, unexpected unhandled values cannot cause fallback errors or logic gaps. Including the wildcard pattern `case _ =>` accounts for any integer outside the specified numeric ranges, confirming that every potential input yields a valid outcome.
## Decision: Array, Map, or Set?
Use `Array` when element order and duplicate values are meaningful, as demonstrated in this study plan. Use `Map` when key-based lookup is the primary operation. Use `Set` when element uniqueness takes precedence over position. Choose a collection based on the required observable behavior rather than current dataset size.
## Try this
Add `StudyTask { title: "Review output", minutes: 5, done: true }` as the third element in the array. Which new line appears, and does `remaining` change?
### Answer
The new output line is `Review output: done`. `remaining` stays at `25 minutes` because the added task is already marked complete, so the `if` body does not execute for it.
## Ready to continue when
You can explain why `StudyTask` is defined as a record, why the collection uses `Array`, why `for` handles iteration, why `if` manages accumulation, and why `match` performs classification.
See [Patterns & Control Flow](/en/docs/language/patterns-control-flow), [Records & Nominal Data](/en/docs/language/records-nominal-data), and [Collections & Comprehensions](/en/docs/language/collections-comprehensions) for detailed reference rules. Continue to [Failures and Resources](/en/docs/learn/failures-resources).
---
route: https://topaz.ooo/v5.20/en/docs/learn/failures-resources
markdown: https://topaz.ooo/docs/learn/failures-resources.md
covers: §12, §13, §13a, §14
# Failures and Resources
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
**Outcome:** Represent expected absence and recoverable failure in values, guarantee scoped cleanup, and distinguish both from a runtime contract fault.
**Prerequisite:** Complete [Data and Control](/en/docs/learn/data-control) and be able to trace a value through `if`, `match`, and `for`.
## Make bad input an expected result
Create `failures-resources.tpz`:
```topaz
record StudyTask {
title: string,
minutes: int,
}
function parseMinutes(text: string) -> Result {
match toInt(text) {
case Some(value) if value > 0 => Ok(value)
case _ => Err("minutes must be a positive integer")
}
}
function makeTask(title: string, minuteText: string) -> Result {
let minutes = parseMinutes(minuteText)?
Ok(StudyTask { title: title, minutes: minutes })
}
function inspect(minuteText: string) -> () {
print("input: {minuteText}")
defer { print("inspection finished") }
match makeTask("Build application", minuteText) {
case Ok(task) => print("ready: {task.title}, {task.minutes} minutes")
case Err(message) => print("cannot add task: {message}")
}
}
inspect("25")
inspect("later")
```
Check and run it:
```bash
topaz check failures-resources.tpz
topaz run failures-resources.tpz
```
The invalid text is handled as data, so the process completes normally:
```text output
input: 25
ready: Build application, 25 minutes
inspection finished
input: later
cannot add task: minutes must be a positive integer
inspection finished
```
`toInt` returns an `Option`. `Some(value)` indicates that a parsed integer is present, while the alternative case covers absence. `parseMinutes` converts that absence and any non-positive input into a descriptive `Result`.
`makeTask` also returns a `Result`. Postfix `?` unwraps `Ok` and immediately returns a compatible `Err`, so no invalid task is constructed. `inspect` handles both cases explicitly.
The `defer` statement belongs to the scope that started the inspection. Its line appears after both successful and failed input, even though execution follows different branches. In a resource-owning function, deferred actions are where cleanup belongs.
## Inspect a broken contract
Now create `intentional-fault.tpz`:
```topaz
let tasks = ["Run first program", "Build application"]
print(tasks[2])
```
Running `topaz run intentional-fault.tpz` exits nonzero:
```text output
error[TPZ4001]: index 2 is out of bounds for an array of length 2
--> intentional-fault.tpz:2:7
|
2 | print(tasks[2])
| ^^^^^^^^
```
This is a `fault`, not an `Err` intended for recovery. The program violated array bounds. The diagnostic identifies the contract and source span instead of inventing a value.
## Decision: absence, recovery, or fault?
Use `Option` when the absence of a value is an ordinary outcome. Use `Result` when the caller can respond to a meaningful error, and use `?` to propagate that error through another compatible `Result`. Use `fault` for a broken contract. Use `defer` independently whenever a scope must perform cleanup on every exit.
## Try this
Add `inspect("0")` to the valid program. Which path runs, and does the deferred line still appear?
### Answer
Zero fails the `value > 0` guard, so the fallback branch prints `cannot add task: minutes must be a positive integer`. The next line is still `inspection finished`, because `defer` runs when the `inspect` scope exits.
## Ready to continue when
You can explain why invalid text becomes an `Err`, why `?` is valid inside `makeTask`, why the deferred line appears twice, and why out-of-bounds access is a fault rather than expected input.
See [Null, Option, Result & Faults](/en/docs/language/null-option-result-faults) and [Defer & Resources](/en/docs/language/defer-resources) for exact rules. Continue to [First Application](/en/docs/learn/first-application).
---
route: https://topaz.ooo/v5.20/en/docs/learn/first-application
markdown: https://topaz.ooo/docs/learn/first-application.md
covers: §17, §21, §22
# First Application
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
**Outcome:** Assemble a locked two-module package, run a selected test along with the package entry, and execute its Python artifact without the `.tpz` project or Topaz compiler.
**Prerequisite:** Complete [Failures and Resources](/en/docs/learn/failures-resources), install `topaz`, and ensure Python 3.11 or newer is available.
## Start with the ordinary scaffold
In the previous five lessons, every exercise ran out of a single source file without a project manifest. In this final guide, you build a complete multi-module package with locked dependencies and targeted build artifacts. That requires a project manifest configuration file named `topaz.toml`.
`topaz.toml` serves as the project manifest configuration. It records package identity, specifies language versions, defines entry points, configures build targets, and declares dependencies. Single-file scripts do not need manifest files because they do not declare dependencies or build targets. Multi-module projects require `topaz.toml` so the compiler can resolve file trees and target settings.
From the directory intended for your project, run:
```bash
topaz init --root study-plan
cd study-plan
```
Running `topaz init --root study-plan` creates a new project directory named `study-plan`. Inside that directory, `init` generates two initial files, `topaz.toml` and `src/main.tpz`. `init` refuses to overwrite existing projects. It creates `topaz.toml` and `src/main.tpz`, but does not create a lockfile or test file. Navigating into `study-plan` with `cd study-plan` positions your terminal inside the project root where subsequent Topaz commands expect to run.
You must initialize the scaffold directory first and navigate into it before adding source files or running build tools. Replace the entry source code and add the module and selected test so the tree structure becomes:
```text tree
study-plan/
├── topaz.toml
├── src/
│ ├── main.tpz
│ └── plan.tpz
└── tests/
└── plan.tpz
```
Creating `src/plan.tpz` and `tests/plan.tpz` establishes a multi-file layout. The manifest `topaz.toml` sits at the project root to govern all files in `src/` and `tests/`.
The scaffold manifest configuration is:
```toml
[package]
name = "study-plan"
version = "0.1.0"
language = "5.20"
entry = "src/main.tpz"
[build]
target = "native"
deterministic = true
[dependencies]
std = "5.20"
```
This manifest establishes project settings. `[package]` sets the package name to `"study-plan"`, the version to `"0.1.0"`, the language version to `"5.20"`, and designates `src/main.tpz` as the main application entry point. `[build]` selects the compilation target, while `[dependencies]` declares standard library requirements.
## Create the two modules
Rather than keeping all code inside a single file, this project divides code across two separate modules, `src/plan.tpz` and `src/main.tpz`. Separating logic into two modules isolates reusable domain models and operations from application entry logic. This division keeps domain code decoupled from program initialization and command-line handling.
`import` and `export` control visibility across module boundaries. `export` explicitly exposes selected records or functions to other modules. Symbols without `export` remain private internal implementation details. `import` pulls exposed symbols into another module scope so they can be referenced directly.
`src/plan.tpz` defines the domain model and reusable operations:
```topaz
export record StudyTask {
title: string,
minutes: int,
done: bool = false,
}
function parseMinutes(text: string) -> Result {
match toInt(text) {
case Some(value) if value > 0 => Ok(value)
case _ => Err("minutes must be a positive integer")
}
}
export function makeTask(
title: string,
minuteText: string,
done: bool = false,
) -> Result {
let minutes = parseMinutes(minuteText)?
Ok(StudyTask { title: title, minutes: minutes, done: done })
}
export function summarize(tasks: Array) -> string {
let mut remaining = 0
for task in tasks {
if !task.done {
remaining = remaining + task.minutes
}
}
"tasks={tasks.length}, remaining={remaining} minutes"
}
```
Only symbols marked with `export` are visible to other modules. `parseMinutes` remains an internal implementation detail.
`src/main.tpz` imports those exported symbols and defines the package entry point:
```topaz
import src.plan { StudyTask, makeTask, summarize }
export function main(args: Array, stdin: string) -> Result {
let tasks: Array = [
makeTask("Run first program", "10", true)?,
makeTask("Build application", "25", false)?,
]
print(summarize(tasks))
Ok(0)
}
```
The explicit `main` function receives command-line arguments and standard input, returning an exit code or an error message. While this application requires neither input stream, maintaining this signature keeps the application interface clear. `src/main.tpz` imports `StudyTask`, `makeTask`, and `summarize` from `src.plan` to create sample tasks and output the summary string.
With production modules established, create a dedicated test file in `tests/plan.tpz`. Adding `tests/plan.tpz`:
```topaz test-profile
import src.plan { StudyTask, makeTask, summarize }
match makeTask("Read syntax map", "15") {
case Ok(task) => assert(task.minutes == 15, "valid minutes")
case Err(message) => assert(false, message)
}
match makeTask("Read syntax map", "later") {
case Ok(_) => assert(false, "invalid minutes were accepted")
case Err(message) => assert(
message == "minutes must be a positive integer",
"invalid minutes",
)
}
let tasks: Array = [
StudyTask { title: "Run first program", minutes: 10, done: true },
StudyTask { title: "Build application", minutes: 25 },
]
assert(
summarize(tasks) == "tasks=2, remaining=25 minutes",
"summary",
)
```
The isolated TestHost environment provides the standard `assert(...)` function for testing. This test file validates correct input, handles invalid input, and verifies the final summary without altering production code.
## Lock, format, check, test, and run
Topaz requires a resolved lockfile before running verification checks or executing code. You create the lockfile first so that all subsequent commands evaluate source code against explicit locked manifest state.
Create and inspect the dependency lockfile:
```bash
topaz lock --root .
```
Running `topaz lock --root .` reads `topaz.toml` in the current directory, resolves package settings, and generates a new file named `topaz.lock`.
The generated `topaz.lock` file contains:
```toml
[[package]]
name = "study-plan"
version = "0.1.0"
source = "root"
manifest_hash = "sha256:780ff549a9fb9b09d27c62df8ce48e70c1fc7b33d6d429e6bb826685f42e7615"
```
This file records package metadata and the calculated manifest hash.
Now run the quality verification commands in sequence. You format code first to check formatting compliance, check types second to validate resolution against `topaz.lock`, and execute tests third to verify logic.
Run the local quality verification loop in order:
```bash
topaz fmt --check --root .
topaz check --root . --locked
topaz test tests/plan.tpz --root . --locked
```
`fmt --check` flags formatting differences without modifying files. `check` validates the resolved unit. The explicit path targets a single test entry point. Running these three commands verifies formatting compliance, resolves source dependencies against `topaz.lock`, and executes the assertions in `tests/plan.tpz`.
The command produces the following test output:
```text output
tests/plan.tpz: test-ok
```
This output confirms that all test assertions in `tests/plan.tpz` passed successfully.
Once formatting, unit resolution, and tests have passed, execute the package entry point.
Run the package entry point:
```bash
topaz run --root . --locked
```
Running `topaz run --root . --locked` executes the entry point defined in `topaz.toml` (`src/main.tpz`) using `topaz.lock`.
The command outputs:
```text output
tasks=2, remaining=25 minutes
```
This output displays the evaluated task summary printed by `main`.
## Build and run without the Topaz project
With local verification and package execution complete, compile the project into a target artifact. Building comes after testing and running to ensure that only verified source code is packaged into distribution files.
The scaffold defaults to native compilation targets, but passing an explicit flag selects an alternative target. Build the Python distribution for this course:
```bash
topaz build --target python --root . --locked --out-dir ../study-plan-product
```
Running `topaz build --target python --root . --locked --out-dir ../study-plan-product` creates the directory `../study-plan-product` and generates Python distribution files inside it.
Python is selected intentionally for this step. It requires Python 3.11 or newer at runtime without requiring a Rust installation for new users. The managed artifact directory contains the following files:
```text tree
study-plan-product/
├── GENERATED-OUTPUT-NOTICE.txt
├── LICENSE
├── NOTICE
├── program.py
├── topaz-artifact.json
└── topaz_py_rt.py
```
This file tree lists all generated build files in `study-plan-product/`. `program.py` contains the compiled program logic, `topaz_py_rt.py` provides runtime support modules, `topaz-artifact.json` stores artifact metadata, and legal notices complete the distribution directory.
To verify that the artifact runs independently without project source code or compiler tools, move the Topaz project directory away before executing `program.py`.
Move the Topaz project directory away, then execute the application from the artifact directory:
```bash
cd ..
mv study-plan source-unavailable
cd study-plan-product
python3 program.py
```
Executing `cd ..` steps out of the project directory. Running `mv study-plan source-unavailable` renames the source project directory so its files are unavailable. Entering `study-plan-product` with `cd study-plan-product` puts the shell into the artifact directory. Executing `python3 program.py` runs the compiled Python program directly using Python 3.
In PowerShell, replace the move command with `Rename-Item study-plan source-unavailable` and run `python program.py`. The execution output matches the package output:
```text output
tasks=2, remaining=25 minutes
```
This output matches the earlier result of `topaz run`, proving that the output artifact executes identically without source `.tpz` files.
The artifact directory includes generated Python source code and runtime support modules. In this context, "without source" means the directory contains no `.tpz` files and operates independently of the Topaz project, compiler, registry, or build workspace.
## Keep the boundaries straight
- A **file** refers to a single `.tpz` source file.
- A **module** represents the namespace exposed by a file through `export`.
- A **unit** is the set of Topaz source files resolved from an entry point and its imports.
- A **package** defines product identity and locked dependencies inside `topaz.toml` and `topaz.lock`.
- A **target** specifies the output compilation format, such as Python.
- An **artifact** is the managed build result prepared for distribution and execution.
## Decision: source loop or product boundary?
Use `check`, `test`, and `run` for fast source feedback. Use `build` for the distributable product, then execute that artifact outside the project directory. Source-tree execution validates program logic; external artifact execution validates the delivery boundary.
## Try this
Modify the minute text for the second task in `src/main.tpz` from `"25"` to `"30"`. Which expectations must change before the full execution loop passes?
### Answer
Executing the package entry point and the Python artifact will output `tasks=2, remaining=30 minutes`. The selected test defines its own 25-minute task fixture, so its existing summary remains valid and passes. This separation demonstrates that the test owns its test fixture rather than depending on `main`.
## Ready to continue when
You can explain all six boundaries defined above, regenerate and enforce the lockfile, run `fmt --check`, `check`, the selected `test`, and `run`, and execute the Python artifact after the `.tpz` project source is moved.
Use [Syntax at a Glance](/en/docs/language-tour) as your reference map, then deepen your understanding with [Modules & Visibility](/en/docs/language/modules-visibility), [Application Loop](/en/docs/guides/application-loop), and [Artifacts & Deployment](/en/docs/toolchain/artifacts-deployment).
---
route: https://topaz.ooo/v5.20/en/docs/learn/reading-topaz
markdown: https://topaz.ooo/docs/learn/reading-topaz.md
covers: §7, §8, §13, §17
# Reading a Real File
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
**Outcome:** Open a file you did not write and locate its shape, contracts, and failure paths without reading line by line.
**Prerequisite:** Complete [First Application](/en/docs/learn/first-application). Writing focused programs and reading large codebases are distinct skills, and this page focuses on the second.
## Reading is not writing in reverse
Reading a large file from top to bottom is rarely the fastest way to answer a specific question. Experienced readers skim for key landmarks, construct a working mental map, and then read relevant sections closely. The same four landmark types provide guidance whenever they appear.
Four passes provide quick orientation. Each pass takes seconds.
## Pass one, the imports
The first lines reveal what the file depends on before you examine any other code.
```topaz
import std.bytes { Bytes }
import std.json
```
Imports reveal named module dependencies, while prelude operations and the selected product profile identify additional host interaction. Read all three together when mapping the host boundary; the import pass is the first map of the file.
## Pass two, the shapes
Look for the nouns. A record declares a shape that values of that kind must have, often conveying the purpose of a file faster than any comment.
```topaz
record StudySession {
topic: string,
minutes: int,
finished: bool
}
function summarize(session: StudySession) -> string {
if session.finished {
return "{session.topic}: done"
}
"{session.topic}: {session.minutes} minutes left"
}
```
Reading only the record, you already know this file handles study sessions with a topic, a duration, and a completion state. You learn the domain context without reading a single line of logic.
## Pass three, the signatures
Now read function signatures and skip every body. A signature acts as a contract, and contracts compose into a cohesive narrative.
In the example above, `summarize` takes a session and returns a string. The signature fixes its input and result shape. A `Result` return would also expose recoverable failure. It does not, by itself, encode every host capability. File and I/O authority also depend on the selected product profile.
The signature pass provides the file's data and recoverable-failure outline. Read imports and the package profile alongside the signatures when evaluating host authority, then descend into the body relevant to your question.
## Pass four, the failure paths
Last, find where the file admits things can go wrong. Look for `Result` in signatures and for the `?` mark in bodies.
A function whose result type is a `Result` indicates that execution can fail and callers must handle it. The `?` mark in a body means failure at that point is passed up to the caller rather than handled here. Together, they outline the error path through the file without requiring detailed reading of error handling logic.
A file with no `Result` exposes no recoverable failure through its signatures. Runtime faults and host operations are separate surfaces found in function bodies and the selected product profile. Keep the distinction between a returned error and a runtime fault.
## What you now do with a real file
Run those four passes on the source of any example in this documentation, or on your own `first-application` project. Imports, shapes, signatures, failure paths. You should be able to describe the purpose of a file in one sentence before reading a single function body closely.
When one of the four passes leaves you stuck, the reference explains the exact form. Shapes are in [Records & Nominal Data](/en/docs/language/records-nominal-data), signatures in [Functions & Generics](/en/docs/language/functions-generics), and failure paths in [Null, Option, Result & Faults](/en/docs/language/null-option-result-faults).
## Try this
Read the `summarize` function again and answer without running it. Can code that receives only the returned string learn from its type whether the session was finished?
### Answer
No. The signature promises a string in both cases, so code holding only that result must inspect the text, which is brittle. If downstream code needs the completion state, return it in a structured result rather than encoding it in a sentence. Noticing this from the signature alone, without reading the body, is the exact purpose of the third pass.
## Ready to continue when
You can open an unfamiliar file, name its dependencies, its main shapes, two of its contracts, and one place it can fail, in under a minute and without reading every line.
The reference material is organized the same way you just read. [Syntax at a Glance](/en/docs/language-tour) is the fastest map of the forms themselves.
---
route: https://topaz.ooo/v5.20/en/docs/language-tour
markdown: https://topaz.ooo/docs/language-tour.md
covers: §1, §1a, §2, §3, §4, §5, §6, §7, §8, §9, §10, §11, §12, §13, §14, §15, §16, §17, §18, §19, §20, §22
# Syntax at a Glance
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Use this page as a quick reference map rather than a full course or a condensed specification. Each card answers one practical question, presents the canonical form, and points to the manual page detailing the underlying mechanics. If you are writing your first Topaz program, start with the [Learning path](/en/docs/learn) and keep this page open for reference.
## The everyday working set
Browse the cards to review syntax at a glance, or jump directly to the specific form you need. The examples are minimal by design. Complete programs and declarations stand on their own. The module card specifies the single file it assumes.
### Values, bindings, and types
Values represent the data a program processes. Bind a value with `const` when it must be known at check time, `let` for an immutable local binding, or `let mut` when reassignment is part of the model. Add `: Type` when an explicit annotation clarifies the contract. Otherwise Topaz infers the type automatically.
**Shape:** `let name: Type = expression`
```topaz
const DEFAULT_MINUTES = 25
let title: string = "Check program"
let mut completed: int = 0
completed = completed + 1
```
**Manual:** [Types](/en/docs/language/types)
### Functions and lambdas
A named function defines reusable behavior with an explicit parameter and return contract. A lambda provides a concise anonymous form for passing behavior as a value. A function body can use `return`, or evaluate its final expression as the result.
**Shape:** `function name(value: Type) -> Result { expression }` and `value => expression`
```topaz
function double(x: int) -> int {
x * 2
}
let increment: (int) -> int = x => x + 1
print("{double(increment(3))}")
```
This program prints `8`.
**Manual:** [Functions & Generics](/en/docs/language/functions-generics)
### Operators and expression results
Operators combine values, while blocks, `if`, `match`, and value-position `for` can produce values of their own. Inside a block, the final expression omits trailing assignments or return keywords and serves directly as the block result.
**Shape:** `{ statements; finalExpression }`
```topaz
let total = {
let base = 20
base + 5
}
print("{total}")
```
This program prints `25`. Assignment is a statement, not an expression.
**Manual:** [Operators & Expressions](/en/docs/language/operators-expressions)
### `if`, `match`, and `for`
Use `if` for Boolean branching, `match` when the shape or case of a value matters, and `for` to iterate over values in sequence. Here `for` appears in value position, collecting each score result into an `Array`.
**Shape:** `if condition { value } else { value }`, `match value { case pattern => value }`, and `for item in values { value }`
```topaz
let scores = [92, 74]
let labels = for score in scores {
match score {
case 90..100 => "A"
case _ => if score >= 70 {
"pass"
} else {
"retry"
}
}
}
```
`labels` evaluates to `["A", "pass"]`.
**Manual:** [Patterns & Control Flow](/en/docs/language/patterns-control-flow)
### Arrays, maps, and sets
Choose `Array` for ordered values, `Map` for key-to-value lookups, and `Set` for unique elements. Array element ordering is index-based. Maps and sets retain their specified insertion order.
**Shape:** `[a, b]`, `map { key: value }`, and `set { a, b }`
```topaz
let scores: Array = [80, 90]
let ids = set { 3, 1, 3 }
let labels = map { 1: "one", 2: "two" }
print("{scores}:{ids.toArray()}:{labels.keys}")
```
The duplicate `3` occupies a single set slot. A map key appears only once in a literal.
**Manual:** [Collections & Comprehensions](/en/docs/language/collections-comprehensions)
### Records, enums, and unions
A `record` defines a product type with named fields. An `enum` defines a closed set of variants. A union written with `|` specifies that a value can match any listed type. Including `null` handles nullable data boundaries.
**Shape:** `record Name { fields }`, `enum Name { variants }`, and `type Choice = A | B`
```topaz
record Task {
title: string,
done: bool = false,
}
enum Filter {
All,
Open,
}
type Selection = Task | null
let selected: Selection = null
```
Records and enums are nominal. Their declared identity matters, not just their visible structure.
**Manual:** [Records & Nominal Data](/en/docs/language/records-nominal-data)
### Absence, recoverable failure, faults, and cleanup
Use `Option` for expected absence and `Result` for recoverable errors that callers handle. Postfix `?` unwraps `Ok` or returns the underlying `Err`. `defer` ties resource cleanup to the current lexical scope. A fault is neither a keyword nor a value. It aborts execution and cannot be caught by `?`.
**Shape:** `Some(value)` / `None`, `Ok(value)` / `Err(error)`, `result?`, and `defer { cleanup }`
```topaz
function readPlan(path: string) -> Result {
let file = open(path)?
defer { file.close() }
file.read()
}
```
An open or read failure yields a `Result`. An invalid array index produces a runtime fault instead.
**Manual:** [Null, Option, Result & Faults](/en/docs/language/null-option-result-faults)
### Strings and registry-tag templates
Double-quoted string literals interpolate expressions via `{expression}`. Built-in registry tags `p`, `r`, `sh`, and `sql` preserve domain structure for paths, regular expressions, shell commands, and parameterized SQL. A tagged value does not automatically trigger its host action.
**Shape:** `"text {value}"` and `tag"text {value}"`
```topaz
let name = "Topaz"
let greeting = "Hello, {name}!"
let path = p"notes/{name}.txt"
```
Backtick templates, dollar-brace interpolation, HTML tags, and user-defined tags are not part of current Topaz syntax.
**Manual:** [Strings & Templates](/en/docs/language/strings-templates)
### Modules and visibility
Each `.tpz` file defines a module. A dotted module path resolves relative to the compilation root. Imports form the file prologue, and only declarations marked `export` are visible to external modules.
**Shape:** `import module.path { Name }` and `export function name(...)`
Assume `src/plan.tpz` exports `StudyTask` and `summarize`:
```topaz
import src.plan { StudyTask, summarize }
export function summary(tasks: Array) -> string {
summarize(tasks)
}
```
The explicit import exposes only the named declarations. It does not expose private items of the module.
**Manual:** [Modules & Visibility](/en/docs/language/modules-visibility)
### Format, check, test, run, and build
The toolchain applies a consistent source and module model across development and distribution. Format source code first, verify that static checks pass, run tests, observe execution via the interpreter, and build the target binary or package.
**Shape:** `topaz [inputs]`
```bash
topaz fmt --check --root .
topaz check --root . --locked
topaz test tests/plan.tpz --root . --locked
topaz run --root . --locked
topaz build --target python --root . --locked --out-dir build
```
`check` produces no artifact. `run` executes immediately. `build` generates a managed build target and may require the selected target toolchain.
**Manual:** [CLI & Diagnostics](/en/docs/toolchain/cli)
## Advanced routes
These forms become relevant once you are comfortable with the core working set. They are intentionally presented with less prominence than the primary cards. Follow only the paths your application requires.
- **Pipelines** — trace left-to-right value flow in [Pipelines & Placeholders](/en/docs/language/pipelines-placeholders).
- **Comprehensions** — build collections with nested selection in [Collections & Comprehensions](/en/docs/language/collections-comprehensions).
- **Protocols** — learn static conformance alongside nominal declarations in [Records & Nominal Data](/en/docs/language/records-nominal-data).
- **Generics** — add one rank-1 type parameter when ordinary functions are not sufficient in [Functions & Generics](/en/docs/language/functions-generics).
- **Concurrency** — join named work with a declared timeout in [Concurrency](/en/docs/language/concurrency).
- **Files and resources** — cross an explicit host boundary in [Files & Resources](/en/docs/stdlib/files-resources).
- **Web and service products** — choose a browser, offline, or managed HTTP service path in [Application Loop](/en/docs/guides/application-loop).
- **Bytes and media** — work with immutable `Bytes`, mutable `ByteBuffer`, hashes, and codecs in [Core Library](/en/docs/stdlib/core).
This page serves as an unnumbered reference guide. Return to the [Learning path](/en/docs/learn) when you want concepts, exercises, and your first complete application presented in dependency order.
---
route: https://topaz.ooo/v5.20/en/docs/guides/application-loop
markdown: https://topaz.ooo/docs/guides/application-loop.md
covers: §13a, §17, §21, §22
# Application Loop
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Use this page after [First Application](/en/docs/learn/first-application). The authoring loop is shared, but the delivery loop is not. Choose a single product target instead of building every target sequentially.
- Choose **native** for a single platform-specific executable with no language runtime at deployment.
- Choose **Python** when Python 3.11 or newer is already present in the deployment environment.
- Choose **Web Application** when Topaz manages the browser model, messages, update function, and view.
- Choose **HTTP service** when a checked request handler runs behind a process supervisor and, for public traffic, a reverse proxy.
Raw Web and Worker packages serve as integration products for an existing JavaScript host. They are covered in [WASM & Playground](/en/docs/toolchain/wasm-playground).
## Shared authoring loop
Start from a checked two-module package. A current native package manifest uses the following layout. Save it as `topaz.toml`:
```toml
[package]
name = "task_summary"
version = "0.1.0"
language = "5.20"
entry = "src/main.tpz"
[build]
target = "native"
deterministic = true
[dependencies]
std = "5.20"
```
The helper module handles the application calculation:
```topaz
export function remainingMinutes(values: Array) -> int {
let mut total = 0
for value in values {
total += value
}
total
}
```
The entry module converts the result into an observable command:
```topaz
import src.summary { remainingMinutes }
export function main(args: Array, stdin: string) -> Result {
let minutes = remainingMinutes([10, 25])
print("remaining={minutes}")
Ok(0)
}
```
Generate `topaz.lock` once, then use the same sequence for routine work:
```bash
topaz lock --root task-summary
topaz fmt --check --root task-summary
topaz check --root task-summary --locked
topaz test --root task-summary --locked
topaz run --root task-summary --locked
```
`fmt --check` reports formatting drift without modifying files. A failed `check` or `test` halts before a product artifact is written. Fix the first Topaz diagnostic and repeat the command. Use `topaz explain TPZ####` when a diagnostic code requires a detailed explanation.
## Path A: native or Python command application
Native builds require a Rust toolchain for the target operating system and architecture. Python builds do not require Rust, but the resulting product requires Python 3.11 or newer.
Build only the product intended for delivery:
```bash
topaz build --release --root task-summary --locked --out-dir native-product
topaz build --target python --root task-summary --locked --out-dir python-product
```
The release tree structures are:
```text output tree
native-product/
├── GENERATED-OUTPUT-NOTICE.txt
├── LICENSE
├── NOTICE
├── target
│ └── release
│ └── program[.exe]
└── topaz-artifact.json
python-product/
├── GENERATED-OUTPUT-NOTICE.txt
├── LICENSE
├── NOTICE
├── program.py
├── topaz-artifact.json
└── topaz_py_rt.py
```
`program[.exe]` specifies `program` on Unix and `program.exe` on Windows. Copy the selected directory completely to a new location and run from its root:
```bash
./target/release/program
python3 program.py
```
Use the first command for native products and the second for Python products. They are alternative paths. If the application declares `[capabilities.fs]`, place its `data/` and writable output directories alongside the product, using that directory as the working directory. Application data is not added to native or Python products automatically.
If a build reports a missing Rust toolchain, either install the matching Rust toolchain or choose Python. If an output directory contains another target or a modified managed file, preserve it for inspection and build into a new empty directory.
## Path B: managed Web Application
A Web Application build requires Rust configured with the `wasm32-unknown-unknown` target. Create the checked scaffold instead of converting the command entry point:
```bash
topaz init --target web-app --root hello-web
topaz lock --root hello-web
topaz fmt --check --root hello-web
topaz check --root hello-web --locked
topaz test hello-web/tests/app.tpz --root hello-web --locked
topaz dev --root hello-web --port 8000
topaz build --root hello-web --locked --release --out-dir web-product
```
The generated entry point uses the checked lifecycle:
```topaz
import std.dom { Html, WebAppEvent, WebAppStep, text }
export record Model {
message: string,
}
export enum Msg {
Ready,
}
export function init() -> WebAppStep {
WebAppStep { model: Model { message: "Hello from Topaz" }, commands: [] }
}
export function update(model: Model, message: Msg, event: WebAppEvent) -> WebAppStep {
WebAppStep { model: model, commands: [] }
}
export function view(model: Model) -> Html {
text(model.message)
}
```
`topaz dev` runs a loopback development server and retains the last valid product after a failed edit. It is not a production server. The final directory contains HTML, the checked application host, the raw Web facade, WASM, capability metadata, declared styles and assets, and the standard license and artifact files. Copy the complete directory to a static host and serve it over HTTP. Opening ES modules through `file://` is not a valid deployment path.
[Data Lens](/en/docs/guides/data-lens) is the maintained multi-module example for this path.
## Path C: HTTP service
Create a service scaffold when the product is a request handler rather than a command or browser UI:
```bash
topaz init --target http-service --root hello-service
topaz lock --root hello-service
topaz fmt --check --root hello-service
topaz check --root hello-service --locked
topaz test --root hello-service --locked
topaz dev --root hello-service --port 8080
topaz build --root hello-service --locked --release --out-dir service-product
```
The handler boundary is explicit and synchronous:
```topaz
import std.http { HttpRequest, HttpResponse, text }
export function handle(req: HttpRequest) -> HttpResponse {
if req.url.path() == "/health" {
return text(200, "ok")
}
text(404, "not found")
}
```
The service product contains one native executable, embedded fixed settings, third-party notices, and standard artifact files. It binds to loopback by default. A non-loopback bind requires an explicit process argument. TLS, HTTP/2, outbound networking, and a general Web framework are outside this product.
[HTTP Service](/en/docs/guides/http-service) completes this path, including budgets, observable failures, source-free execution, and deployment recovery.
## Related documentation
- [First Application](/en/docs/learn/first-application)
- [Data Lens](/en/docs/guides/data-lens)
- [HTTP Service](/en/docs/guides/http-service)
- [Packages & Distribution](/en/docs/toolchain/packages)
- [Artifacts & Deployment](/en/docs/toolchain/artifacts-deployment)
---
route: https://topaz.ooo/v5.20/en/docs/guides/lispex-application
markdown: https://topaz.ooo/docs/guides/lispex-application.md
covers: §0, §20, §21
# Run Lispex Rules
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Some decisions do not belong directly in your source code. Business logic such as request approvals, customer tier classifications, or claim scoring change on their own schedule and are often written by external teams. This guide demonstrates how to isolate such a rule in its own file, supply an input, and receive a decision without allowing the rule to access any other system resources.
The rules are written in [Lispex](/en/docs/interop/lit), a deterministic Lisp designed for decision logic where a given rule and input always yield the identical result. Topaz reads each rule during package compilation and follows a prepare-once/evaluate-many runtime model. A prepared rule cannot access files, read the system clock, or connect to the network, and it terminates once it reaches the allocated execution limit. A package built in this manner constitutes a complete-current-profile Lispex decision application, which this page constructs step by step.
This page assumes prior experience building a Topaz package. If you are new to Topaz, start with [First Application](/en/docs/learn/first-application).
## Write the rule
A rule is a standard Lispex source file. The following example compares two numbers and returns one of two string values. Save it as `rules/approve.lspx`:
```lispex
(if (< 10 15) "allow" "deny")
```
Three additional rules complete the application package below. The file `rules/classify.lspx` returns a numeric value rather than a string:
```lispex
(+ 20 22)
```
The rule `rules/deadline-probe.lspx` executes indefinitely on its own, providing a workload for the application deadline mechanism to terminate:
```lispex
(letrec ((loop (lambda () (loop)))) (loop))
```
The fourth rule reuses `rules/approve.lspx` under a much lower limit, requiring no separate source file.
## Set two kinds of limit
Two distinct documents define resource ceilings for execution. A limits document applies to an individual rule, while a quotas document applies to the entire application.
The file `rules/approve.limits.json` defines the resource ceilings for preparing and evaluating a single rule. A complete document must retain every field, and while lower values may be specified, fields cannot be omitted or added. The following snippet illustrates the two work ceilings:
```json
{
"schema": "topaz.lispex-embed-limits/v1",
"prepare": {
"prepare_work": 1000000
},
"evaluate": {
"eval_work": 10000
}
}
```
The `prepare_work` and `eval_work` fields measure evaluator computational effort deterministically rather than tracking elapsed wall-clock time, ensuring that identical rules and inputs consume identical resource units across different host environments.
Every rule requires a dedicated limits document. Both `rules/classify.limits.json` and `rules/deadline-probe.limits.json` share this structural format. The document `rules/semantic-limit-probe.limits.json` uses the same structure with `eval_work` set to `1`, an intentionally insufficient threshold that prevents the rule from completing. This setup allows the application to demonstrate how resource exhaustion is handled.
The file `rules/application.quotas.json` imposes constraints on the host runtime rather than an individual rule. It permits up to two concurrent evaluations, two queued evaluations, 64 total evaluations, and a finite wall-clock timeout.
The distinction between limits and quotas becomes critical when execution is interrupted. A rule exceeding its `eval_work` ceiling reaches a semantic limit failure, returning a result type that application code can inspect and pattern-match. Conversely, an application exceeding its global quota rejects the invocation entirely. Because these represent distinct typed outcomes, application logic must distinguish between them.
## Declare the package
The manifest configuration connects all components of the application. It selects the Topaz language version and standard library, specifies the native delivery target, declares the complete current Lispex profile and application contract, references the quotas document, and registers each rule. Save it as `topaz.toml`:
```toml
[package]
name = "complete_lispex_decision_application"
version = "0.1.0"
language = "5.20"
entry = "src/main.tpz"
[build]
target = "native"
deterministic = true
[dependencies]
std = "5.20"
[lispex]
profile = "lispex/r7rs-rule-current-profile-bounded/1"
application = "topaz/lispex-decision-application/2"
application_quotas = "rules/application.quotas.json"
[[lispex.rule]]
name = "approve"
source = "rules/approve.lspx"
limits = "rules/approve.limits.json"
[[lispex.rule]]
name = "classify"
source = "rules/classify.lspx"
limits = "rules/classify.limits.json"
[[lispex.rule]]
name = "deadline_probe"
source = "rules/deadline-probe.lspx"
limits = "rules/deadline-probe.limits.json"
[[lispex.rule]]
name = "semantic_limit_probe"
source = "rules/approve.lspx"
limits = "rules/semantic-limit-probe.limits.json"
```
The `language` and `std` settings are prescribed by the application contract rather than user-selected, causing toolchains operating in a different language mode to reject the package during preparation. Refer to [Toolchain Status](/en/docs/toolchain/status) for details on the compiler, language mode, and runtime included in an installation.
The `profile` key selects the complete-current-profile evaluator identified by `lispex/r7rs-rule-current-profile-bounded/1`. In this provider-owned identifier, `bounded` names the fixed resource envelope. The embedded-core compatibility profile uses its own component and contract identity. The `application` key selects the 5.20 delivery contract. Both values are fixed identifiers.
Each `[[lispex.rule]]` entry defines three standard fields. The `name` field maps to a generated function within `std.lispex.rules`, turning `approve` into `rules.approve()`. This identifier is neither a runtime file path nor a component selector. Multiple rule entries can reference the same source file—as demonstrated by `approve` and `semantic_limit_probe`—while applying distinct execution limits.
## Lock the package
Locking prepares each rule once and records the exact compilation artifacts. Execute the command from the package root:
```bash
topaz lock --root .
```
Never enter a component or prepared-artifact digest manually. Inspect `topaz.lock` prior to committing it to source control. Its `[lispex]` section locks the profile, application contract, component, evaluator, ABI, value codec, meter, artifact contract, adapter, quotas, target disposition, and catalog of generated handles. Each `[[lispex.rule]]` row locks the corresponding source, limits, preparation request, submission, and compiled artifact.
The component represents the evaluator itself. The lock pins its state via a cryptographic digest to prevent subsequent builds from silently executing a modified component.
Modifying the manifest, a rule source, a limits document, application quotas, the component, or the profile requires regenerating the lock file. Running a build with the `--locked` flag rejects any configuration drift instead of silently preparing modified rules.
## Call a rule from Topaz code
The lock operation generates the core API within `std.lispex` alongside a dedicated function for each declared rule inside `std.lispex.rules`.
```topaz
let settlement = evaluate(rules.approve(), input, defaultLimits(rules.approve()))
let recorded = evaluateWithEvidence(
rules.approve(),
input,
defaultLimits(rules.approve()),
)
```
The `evaluate` function returns a settlement value representing the typed result of an evaluation. This settlement indicates whether the rule executed to completion, encountered an internal evaluation failure, or exhausted a resource limit. The `defaultLimits` function retrieves the execution ceilings recorded in the lock file, preventing calls from requesting higher resource limits than declared by the package.
The `input` parameter must be a canonical value using a shared byte encoding scheme between the host and evaluator to ensure consistent semantics. Each evaluation initializes a clean guest memory state, global variables, execution meters, and transcript data, ensuring that state is not shared between consecutive rule evaluations.
Use `evaluate` when only the evaluation result is required. Use `evaluateWithEvidence` when execution must produce a verifiable audit record alongside the deterministic result.
## Store, verify, and replay the evidence
When `evaluateWithEvidence` resolves to an eligible outcome, it generates a consumer artifact. This artifact encapsulates the evaluation execution, allowing it to be stored, shared, and independently validated without relying on the host application that generated it.
The complete lifecycle consists of six calls:
1. `consumerArtifactBytes` serializes the artifact into a byte payload suitable for storage.
2. `consumerArtifactFromBytes` deserializes stored bytes while performing validation checks.
3. `inspectConsumerArtifact` inspects artifact metadata and identities without performing code execution.
4. `verifyConsumerArtifact` validates internal structure, cryptographic digests, and parameter bindings.
5. `portableCoreBytes` extracts the embedded Lispex-formatted core from the artifact if present.
6. `freshReplay` re-evaluates the locked rule with the identical input in an isolated guest environment, validating that it produces a matching artifact.
Be careful when evaluating assertions derived from an artifact. The artifact and its portable core are produced client-side without cryptographic authentication. They do not specify an issuing identity, provider authorization, signing authority, component admission, or execution permissions for external actions. Furthermore, operational refusals, cancellations, safety preemptions, and engine failures do not generate a portable core.
## Check and run the package
Validate the package before running execution tests, executing both operations against the locked configuration:
```bash
topaz check --root . --locked
topaz run --root . --locked -- all
```
Passing the `all` argument runs a test scenario that exercises the entire application boundary in a single pass. It processes 24 standard inputs, verifies state isolation between rules, triggers semantic resource exhaustion in one rule, and confirms that the application rejects the 65th evaluation attempt after reaching its quota of 64. The reference package implementation produces the following single-line output for `all`:
```text
all:default:24:evidence:verified:replayed:isolation:2:complete:semantic-limit:exhausted:aggregate-quota:64:refused
```
Any differing output text or a non-zero exit code signifies a test failure.
The `deadline_probe` rule is intentionally excluded from the `all` test suite. Release validation tests handle timeout behaviors separately by duplicating the package, reducing the wall-clock quota from 100 milliseconds to 1 millisecond, updating the lock file, and asserting a `DeadlineExceeded` result across both interpreter and native release builds. The standard 100-millisecond configuration must then execute successfully. Isolating this test ensures that the output line above remains independent of host CPU performance.
## Build the native product
Compile an optimized native executable into an output directory located outside the package root:
```bash
topaz build --root . --locked --release --out-dir ../complete-product
```
Execute the binary on the same operating system and hardware architecture used for the build:
```bash
../complete-product/target/release/program all
```
On Windows platforms, execute:
```powershell
..\complete-product\target\release\program.exe all
```
The native executable must produce the identical output line. A successful build does not make the generated executable portable across different target architectures.
## Where the finished application can run
The application contract specifies a fixed set of supported deployment target routes. Any rejected route is denied prior to code generation or execution, without falling back to the interpreter or native target.
| Route | Disposition |
| ------------------------- | ----------------------------------------------------- |
| `interpreter` | Supported for the locked complete-profile application |
| `native` | Supported only on admitted native release targets |
| `generated-python` | Refused before artifact output |
| `raw-web` | Refused before artifact output |
| `worker-web` | Refused before artifact output |
| `managed-web` | Refused before artifact output |
| `http-service` | Refused before artifact output |
| `no-capability` | Refused before execution or artifact output |
| `mcp-empty-component-set` | Refused before execution |
The Topaz 5.18 route remains available for existing packages through the embedded-core compatibility profile. It retains its own component, profile, contract, and prepared-artifact identity. The complete-current-profile application uses the identifiers shown in this guide, and selectors never substitute one route for the other.
## Related documentation
- [Lispex Evaluator and LIT](/en/docs/interop/lit)
- [Interop Boundaries](/en/docs/interop/overview)
- [Rust Backend](/en/docs/interop/rust)
- [Python Backend](/en/docs/interop/python)
- [Packages & Distribution](/en/docs/toolchain/packages)
---
route: https://topaz.ooo/v5.20/en/docs/guides/data-lens
markdown: https://topaz.ooo/docs/guides/data-lens.md
covers: §3, §7, §13a, §17, §21, §22
# Data Lens
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Data Lens is a maintained multi-module web application example. It opens user-selected CSV or JSON files, parses and filters rows locally, displays typed summaries, exports text, and persists only view preferences. Imported documents and parsed rows are neither sent to a server nor stored in durable browser state.
Complete [First Application](/en/docs/learn/first-application) before reading this guide. You should already be comfortable with modules, `Result`, tests, `topaz.lock`, and the `--locked` flag. Building the final browser product requires Rust with the `wasm32-unknown-unknown` target.
## Why this is a Web Application
Choose `web-app` when Topaz owns the model, messages, update function, and view. Choose raw `web` or `web-worker` only when an existing JavaScript host owns the UI or worker protocol. The Playground is designed for quick source experiments, not for deploying Data Lens.
The maintained source is organized by responsibility rather than contained in a single entry file:
```text output
data-lens/
topaz.toml
topaz.lock
src/main.tpz
src/model.tpz
src/session.tpz
src/view.tpz
tests/application.tpz
tests/exporting.tpz
tests/parsing.tpz
tests/transform.tpz
styles/app.css
registry/lens_labels/1.0.0/...
vendor/lens_labels/1.0.0/...
data-lens-core/
topaz.toml
src/lib.tpz
```
`data-lens-core` handles CSV and JSON parsing, filtering, sorting, summaries, and text export. The Web package manages browser messages, local-state encoding, and the view. `lens_labels` is a locked registry dependency copied into `vendor`.
## Model the data before the UI
The application model keeps source text, controls, typed rows, summary state, and visible errors separate:
```topaz
import data_lens_core { DataFormat, DataRow, SortMode, Summary }
export record Model {
source: string,
query: string,
activeOnly: bool,
format: DataFormat,
sortMode: SortMode,
rows: Array,
summary: Summary,
status: string,
error: Option,
}
```
Parsing returns a `Result` containing a stable error location such as `JSON $[0].category: missing field`. The update function stores the message in `error`, which the view renders as plain text. Source content is never inserted with `innerHTML`.
The lifecycle initializes with an in-memory sample and requests only saved view preferences:
```topaz
export function init() -> WebAppStep {
let base = Model {
source: CSV_SAMPLE,
query: "",
activeOnly: false,
format: DataFormat.Csv,
sortMode: SortMode.ByName,
rows: [],
summary: emptySummary(),
status: "Ready",
error: None,
}
WebAppStep {
model: applyModel(base),
commands: [loadState("data-lens-session-load", "session", Msg.LocalCompleted)],
}
}
```
Explicit user selection supplies the document as UTF-8 text. Saving a session writes the query, active-row filter, and sort mode. User cancellation, malformed UTF-8, unavailable storage, quota exhaustion, and corrupt saved state remain typed application outcomes.
## Shortest maintained task path
Create the registry input and sibling core package while authoring the application, then run the vendor workflow once. `vendor` verifies and copies the registry dependency while writing the lock file. Do not run `lock` first for a registry dependency that has not been vendored.
```bash
topaz vendor --root data-lens --from data-lens/registry
topaz fmt --check --root data-lens
topaz check --root data-lens --locked
topaz test data-lens/tests/parsing.tpz --root data-lens --locked
topaz test data-lens/tests/transform.tpz --root data-lens --locked
topaz test data-lens/tests/application.tpz --root data-lens --locked
topaz test data-lens/tests/exporting.tpz --root data-lens --locked
topaz dev --root data-lens --port 8000
topaz build --root data-lens --locked --release --out-dir data-lens-product
```
`topaz run` is the direct execution path for command entries containing `main`. Because Data Lens follows a Web lifecycle, its observable development command is `topaz dev`.
In the browser, verify CSV and JSON format handling, Unicode labels and filenames, filtering, both sort modes, text export, preference state management (saving, reloading, and resetting), cancellation, malformed document handling, and recovery after a failed edit. `topaz dev` continues serving the last successful product until the source is valid again.
## Product and run without the Topaz package source
The managed product directory contains:
```text output tree
data-lens-product/
├── GENERATED-OUTPUT-NOTICE.txt
├── LICENSE
├── NOTICE
├── index.html
├── styles
│ └── app.css
├── topaz-app.js
├── topaz-artifact.json
├── topaz-web-capabilities.json
├── topaz-web.d.ts
├── topaz-web.js
└── topaz-web.wasm
```
Copy the entire directory to a fresh static root and serve it over HTTP. The product does not require the package source, Topaz CLI, registry, npm runtime, CDN, or a data service. If ES modules or WASM fail under `file://`, use a local static HTTP server. If the build rejects the output directory because a managed file changed or another target owns it, keep the directory intact and choose a new empty `--out-dir`.
## Related documentation
- [First Application](/en/docs/learn/first-application)
- [Application Loop](/en/docs/guides/application-loop)
- [Files & Resources](/en/docs/stdlib/files-resources)
- [Packages & Distribution](/en/docs/toolchain/packages)
- [Artifacts & Deployment](/en/docs/toolchain/artifacts-deployment)
---
route: https://topaz.ooo/v5.20/en/docs/guides/http-service
markdown: https://topaz.ooo/docs/guides/http-service.md
covers: §3, §7, §13a, §17, §21, §22
# HTTP Service
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Choose `http-service` when a checked Topaz function receives an `HttpRequest` and returns an `HttpResponse`. The generated native host owns the listener, HTTP/1.1 framing, finite request budgets, overload responses, logging, and shutdown.
This is not a general web server framework. It does not grant outbound networking, raw sockets, ambient environment access, shared mutable Topaz state, TLS, HTTP/2, WebSockets, or sessions. Place a standard reverse proxy in front of the process for public TLS and Internet-edge policy. Treat this explicit `Capability` boundary as part of the product design.
Complete [First Application](/en/docs/learn/first-application) first. Building the service requires a Rust toolchain for the target deployment platform.
## Create, lock, and test
The scaffold supplies the package manifest, service defaults, and a concrete handler:
```bash
topaz init --target http-service --root hello-service
topaz lock --root hello-service
topaz fmt --check --root hello-service
topaz check --root hello-service --locked
topaz test --root hello-service --locked
```
The entry module exports exactly one handler with this shape:
```topaz
import std.http { HttpRequest, HttpResponse, text }
export function handle(req: HttpRequest) -> HttpResponse {
if req.method == "GET" && req.url.path() == "/health" {
return text(200, "ok")
}
text(404, "not found")
}
```
The checker rejects missing, generic, variadic, defaulted, or differently typed handlers before the listener can start.
## Develop through loopback
Run the generated service on loopback and request its health route:
```bash
topaz dev --root hello-service --port 8080
curl --fail-with-body http://127.0.0.1:8080/health
```
`topaz dev` forces the bind address to `127.0.0.1`, rebuilds the managed service, and forwards interruption signals to it. If port 8080 is occupied, choose another loopback port with `--port`.
## Keep finite service budgets
The scaffold records fixed service settings under `[service]`:
```toml
[service]
bind = "127.0.0.1"
port = 8080
workers = 1
max_connections = 64
queue_capacity = 32
max_target_bytes = 8192
max_header_bytes = 16384
max_headers = 64
max_body_bytes = 1048576
header_timeout_ms = 5000
body_timeout_ms = 5000
handler_timeout_ms = 1000
shutdown_grace_ms = 5000
log_format = "text"
```
Unknown keys and out-of-range values fail during package loading. Each request receives a fresh runtime context and module graph. Requests use separate Topaz heaps. Queue saturation, oversized inputs, handler deadlines, and invalid responses produce the declared host outcomes.
## Build and inspect the product
```bash
topaz build --root hello-service --locked --release --out-dir hello-service-product
cd hello-service-product
./target/release/program --print-config
./target/release/program
```
On Windows, run `target\release\program.exe`. The product tree is:
```text output tree
hello-service-product/
├── GENERATED-OUTPUT-NOTICE.txt
├── LICENSE
├── NOTICE
├── THIRD-PARTY-NOTICES.txt
├── target
│ └── release
│ └── program[.exe]
├── topaz-artifact.json
└── topaz-service-config.json
```
`program[.exe]` denotes `program` on Unix and `program.exe` on Windows. `--print-config` validates and prints effective settings without opening a listener. The managed JSON file records embedded defaults. Runtime overrides require explicit command-line flags. An explicit non-loopback bind is configured as follows:
```bash
./target/release/program --bind 0.0.0.0 --port 8080
```
Use that flag only behind the intended network boundary. The manifest itself may specify only a loopback address.
## Observable failures
- Oversized bodies, targets, or header sets produce the declared HTTP errors where framing permits a response.
- A full handler queue returns `503`. A handler deadline returns `504`.
- A runtime fault or invalid application response returns a generic `500` without exposing source paths, request content, or Topaz fault text.
- Responses and host logs share a request ID. Logs omit targets, bodies, and header values.
- `SIGINT` and, on Unix, `SIGTERM` stop admission and drain existing work until `shutdown_grace_ms`.
If `check` or `test` fails, resolve the Topaz diagnostic and repeat the command. If a build detects a modified managed file or another target in the output directory, retain that directory for inspection and choose a new empty `--out-dir`.
## Source-free deployment
Copy the complete `hello-service-product/` directory so the executable, configuration record, integrity manifest, licenses, and notices stay together. The copied product requires neither package source files, the Topaz CLI, a registry, nor the temporary Cargo workspace. Run it under a process supervisor, retain the generated budgets, and terminate public TLS at a reverse proxy.
## Related documentation
- [First Application](/en/docs/learn/first-application)
- [Application Loop](/en/docs/guides/application-loop)
- [Packages & Distribution](/en/docs/toolchain/packages)
- [Artifacts & Deployment](/en/docs/toolchain/artifacts-deployment)
---
route: https://topaz.ooo/v5.20/en/docs/language/lexical-layout
markdown: https://topaz.ooo/docs/language/lexical-layout.md
covers: §1, §1a
# Lexical Structure & Layout
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Consult this page when a `.tpz` file appears syntactically valid line by line, but item boundaries or identifier and literal spellings remain uncertain. Topaz accepts Unicode names and evaluates completed lines as finished statements. Parentheses, brackets, and continuation tokens permit expressions to span multiple lines without an explicit line-continuation character.
## Start with a complete source file
Save this as `lexical-layout.tpz`:
```topaz
let ready = true
let archived = false
let missing = null
let 상태 = "ready"
let total =
40 +
2
print("{상태}: {ready}, {archived}, {missing}, {total}")
```
Check and run it:
```bash
topaz check lexical-layout.tpz
topaz run lexical-layout.tpz
```
The output is:
```text output
ready: true, false, null, 42
```
Across these five bindings, the program demonstrates four literal categories: boolean values `true` and `false`, the absence value `null`, a string, and decimal numbers. `상태` functions as a standard identifier. Keywords such as `let`, `true`, `false`, and `null` retain their English spellings, whereas custom identifiers can incorporate Unicode letters, underscores, trailing digits, and emoji.
## Read the line breaks from what the parser can see
The first four bindings each complete on a single line, allowing each newline to separate distinct top-level items. The `total` expression continues because `=` and `+` appear at line endings. A trailing operator signals to the parser that another operand follows.
Within parentheses, brackets, record braces, parameter lists, and other delimited constructs, newlines function as whitespace rather than item separators. Blocks, `match` bodies, and `concurrent` bodies follow different rules. Because their contents consist of statement lists, a completed line can conclude an item. Wrap complex expressions in parentheses to resolve potential ambiguity rather than relying on indentation. Indentation aids readability, whereas delimiter and continuation rules govern structural parsing.
| You want to… | Prefer |
| ------------------------------------------------ | -------------------------------------------------- |
| End a complete top-level item or block statement | Put the next item on a new line |
| Continue arithmetic or a pipeline | Leave the operator at the end of the previous line |
| Break an argument or collection list | Keep the items inside its delimiters |
| Remove any doubt about grouping | Add parentheses |
Semicolons serve as valid statement separators where statements are permitted, though standard Topaz code omits them. A newline preceding `else` remains part of the surrounding `if` or timed `concurrent` expression.
## Names, comments, and strings
An identifier begins with a Unicode letter, `_`, or emoji, followed by optional Unicode digits. The standalone `_` is reserved. It functions as a pattern wildcard and a pipeline argument placeholder rather than a readable value or bindable name. Identifiers such as `_internal` operate as standard names.
Use `//` for line comments and `/* ... */` for block comments. Block comments do not nest. Integer and floating-point literals use decimal notation. Exponent notation such as `1e21` is not part of the surface, so write the digits in full or compute the value. Alternate numeric bases and underscore digit separators are not canonical.
Double-quoted strings support escape sequences such as `\n` alongside `{expression}` interpolation. Triple-quoted strings provide multiline formatting. Their closing delimiter sets the exact space-and-tab prefix removed from nonblank lines. Topaz preserves Unicode scalar sequences without implicit normalization.
## Common correction
Do not place a binary operator at the start of a statement line when it could be parsed as a unary `+` or `-`. Keep `+` or `-` at the end of the preceding line, or enclose the entire expression in parentheses. Avoid using `s[i]`, `s.length`, or slice syntax for text manipulation. Inspect strings using `s.scalars()`. The [Strings & Templates](/en/docs/language/strings-templates) page details this design approach.
Token recognition follows the longest valid match rule. Consequently, `...`, `..`, `..<`, `?.`, and postfix `?` remain distinct. Single-quoted strings, character literals, nested block comments, string slicing, grapheme-cluster APIs, and implicit Unicode normalization are excluded from the current surface.
Consult [Operators & Expressions](/en/docs/language/operators-expressions) for value-producing expressions and [Types](/en/docs/language/types) for annotations and inference. Refer to [Forbidden & Deferred Forms](/en/docs/language/forbidden-deferred) when a familiar syntax pattern is rejected.
---
route: https://topaz.ooo/v5.20/en/docs/language/operators-expressions
markdown: https://topaz.ooo/docs/language/operators-expressions.md
covers: §2, §5, §11, §12, §13
# Operators & Expressions
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Use an expression when you need a value. Arithmetic, comparison, function calls, and `if` or `match` constructs can all produce values. Assignment serves a different role. It mutates an existing location and is a statement, not a value embedded within another expression.
## Start with a value you can observe
Save this as `operators-expressions.tpz`:
```topaz
function score(base: int, bonus: int) -> int {
let doubled = base * 2
if bonus > 0 { doubled + bonus } else { doubled }
}
let result = score(20, 2)
let exact = result == 42
let changed = result != 41
print("{result} {exact} {changed}")
```
Check and run it:
```bash
topaz check operators-expressions.tpz
topaz run operators-expressions.tpz
```
The output is:
```text output
42 true true
```
`base * 2` evaluates before `+ bonus`, and the selected `if` branch becomes the function result. `==` determines whether two comparable values are equal, while `!=` determines whether they are unequal. Neither operator mutates a binding.
## Choose an operator by its job
| Need | Form | Important distinction |
| -------------------- | ---------------------------------- | ----------------------------------------------------------- |
| Arithmetic | `+`, `-`, `*`, `/`, `%`, `**` | Numeric operands remain in the same `int` or `float` domain |
| Ordering or equality | `<`, `<=`, `>`, `>=`, `==`, `!=` | Only comparable types are permitted |
| Membership | `value in collection` | A Map lookup uses its keys, not `value in map` |
| Conditional work | `left && right`, `left \|\| right` | The right side executes only when needed |
| Default for absence | `value ?? fallback` | Coalesces a nullable or optional value |
| Result propagation | postfix `value?` | Returns an `Err` from the enclosing function, unlike `??` |
| Left-to-right flow | `value \|> next` | Uses pipeline rules rather than ordinary binary arithmetic |
| Mutation | `target = value`, `+=`, `??=` | A statement that does not produce a value |
`&&` evaluates its right side only when the left side is `true`. `||` evaluates its right side only when the left side is `false`. This short-circuit behavior guards work that is valid only after a preceding condition succeeds. `??` likewise skips its fallback when the left side already contains a value. Postfix `?` differs as well. The [Null, Option, Result & Faults](/en/docs/language/null-option-result-faults) page explains its enclosing-function boundary.
## Numeric and equality boundaries
`int` is a signed 64-bit integer. Integer division truncates toward zero, and the remainder has the sign of the dividend. Integer overflow, division by zero, and a dynamically negative integer exponent produce runtime faults. The same invalid operation in a constant expression produces a static error. `float` uses IEEE-754 binary64, so floating division by zero and NaN follow that model. Topaz does not implicitly convert between `int` and `float`.
Primitive values, compatible records, Arrays, Options, Results, and nominal values are comparable only when their contained types are comparable. Array equality observes length and element order. Map and Set values, functions, resources, and template values are not comparable. Nominal equality requires matching enum, record, or newtype identities.
Precedence places calls and member access above exponentiation, unary operators, multiplication, addition, ranges, comparisons, `&&`, `||`, `??`, composition, and finally `|>`. Memorizing the entire hierarchy is rarely necessary. Use parentheses whenever grouping clarifies intent.
## Common correction
Do not write `count++`, `count--`, or `count **= 2`. These forms are not valid in Topaz. For a mutable binding, write an explicit statement such as `count = count + 1`. Do not use `=` when evaluating equality. `=` mutates a target, whereas `==` evaluates to a boolean value.
Bitwise operators, implicit numeric promotion, floating remainder as an operator, operator overloading, and user-defined operators are outside the current surface. Function composition exists only at the specification level for this documentation profile. Current examples use named functions or `|>`.
Continue with [Types](/en/docs/language/types) for the domains operators accept, [Patterns & Control Flow](/en/docs/language/patterns-control-flow) for value-producing branches, and [Pipelines & Placeholders](/en/docs/language/pipelines-placeholders) for `|>`.
---
route: https://topaz.ooo/v5.20/en/docs/language/types
markdown: https://topaz.ooo/docs/language/types.md
covers: §3, §4, §5, §7, §22
# Types
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
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:
```text 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`, and `string`. The unit type has the sole source spelling `()`; the identifier `unit` is not an alias for it. Common containers use named constructors such as `Array`, `Map`, and `Set`. Expected absence and recoverable failure are represented by `Option` and `Result`.
## Choose the shape that states your intent
| Need | Prefer | Why |
| --------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------- |
| An obvious local value | inference | The initializer already conveys the type. |
| A public input, result, or important local boundary | an annotation | The checker and reader directly see the intended contract. |
| One value from a closed set of alternatives | a union or literal-union alias | Each admitted alternative remains explicit. |
| A value that can be called later | a callable type such as `(int) -> bool` | Parameter and return types stay attached to the value. |
| Domain identity, fields, or named cases | a `record`, `enum`, or `newtype` | Nominal 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](/en/docs/language/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:
```text
let names: Array = []
let selected: Option = 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`, `Map`, and `Set`. 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`.
- Every use of a type parameter, primitive named type, builtin generic constructor, user `record`, `enum`, or `newtype`, or type alias supplies exactly its declared number of type arguments.
- The closed source-spellable standard-library types `template`, `JSONValue`, `File`, `Bytes`, `ByteBuffer`, `Path`, `Regex`, `Match`, `TOMLValue`, `URL`, `Date`, `BigInt`, `Decimal`, and `RoundingMode` take no type arguments.
- `File` in a source annotation denotes the same opaque resource type returned by `open`.
- Generic type parameters are invariant. Compatibility follows declared type arguments, not content similarity.
- Generic bounds belong only to named function type parameters. [Functions & Generics](/en/docs/language/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](/en/docs/learn/values-functions). For choosing among absence, recoverable failure, and faults, continue to [Null, Option, Result & Faults](/en/docs/language/null-option-result-faults).
---
route: https://topaz.ooo/v5.20/en/docs/language/bindings-scope-closures
markdown: https://topaz.ooo/docs/language/bindings-scope-closures.md
covers: §3, §4, §5, §7, §18, §22
# Bindings, Scope & Closures
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
A binding associates a name with a value. Its declaration answers two essential questions: can the name be reassigned, and where is it visible? A closure can keep that binding alive even after the enclosing function returns.
## Watch one captured binding change
Save this as `bindings-scope-closures.tpz`:
```topaz
function makeCounter(start: int) -> () -> int {
let mut current = start
() => {
current = current + 1
current
}
}
let label = "outer"
let next = makeCounter(10)
{
let label = "inner"
print("{label}: {next()}, {next()}")
}
print(label)
```
Check and run it:
```bash
topaz check bindings-scope-closures.tpz
topaz run bindings-scope-closures.tpz
```
Both calls share a single counter, while the inner `label` disappears when its block ends:
```text output
inner: 11, 12
outer
```
## Follow the binding, not a copied value
`makeCounter` creates the mutable binding `current` and returns a lambda. The lambda references `current`, capturing that binding directly. Because the original declaration used `let mut`, reassignment inside the lambda is valid.
The closure does not receive a frozen copy of `10`. It retains the same mutable cell. The first call updates the cell to `11`. The second call reads that new value and updates it to `12`. The cell remains alive because `next` still refers to it after `makeCounter` returns.
`label` demonstrates lexical scope. The inner block may shadow the outer name with its own binding. Inside the block, `label` evaluates to `"inner"`. Once the block ends, that binding goes out of scope and the outer `"outer"` binding becomes visible again. Redeclaring the same name twice in one scope is a static error.
## Choose the binding deliberately
| Form | Use it for | Reassignment |
| --------- | -------------------------------------------------------------------------- | ------------ |
| `const` | A compile-time value built from the documented constant-expression grammar | Not allowed |
| `let` | An ordinary runtime value that keeps the same binding | Not allowed |
| `let mut` | State whose reassignment is part of the model | Allowed |
Prefer `let`. Add `mut` only when a subsequent assignment is meaningful. This keeps changing state visible at the declaration instead of making every name potentially mutable.
Parameters and names introduced by patterns are immutable. If a function needs changing local state, copy the parameter into a new `let mut` binding with a name that describes that state.
## Common mistake: treating capture as a snapshot
Capturing an immutable binding is read-only. Capturing a mutable binding preserves its live cell, so writes made through one closure are visible to later reads through the same binding. If two closures capture the same mutable binding, they share that cell rather than receiving independent counters.
Scope still applies when a closure is created. It may capture names that are lexically visible at that point, but it cannot reach a name declared later or a sibling block’s private binding.
## Exact limits
- Inner scopes may shadow an outer name. One scope may not declare the same name twice.
- An immutable `let`, a `const`, a parameter, or a pattern binding cannot be assigned.
- Mutable declarations accept a bare identifier rather than a destructuring pattern.
- Topaz closures have no explicit capture list, move-capture syntax, Rust closure traits, or borrow-receiver syntax.
- Resource lifetime is a separate decision. The File-only `using` form and deterministic cleanup belong in [Defer & Resources](/en/docs/language/defer-resources).
Revisit [Values and Functions](/en/docs/learn/values-functions) for the initial immutable and mutable bindings. Continue with [Functions & Generics](/en/docs/language/functions-generics) to choose between named functions, lambdas, and generic functions.
---
route: https://topaz.ooo/v5.20/en/docs/language/patterns-control-flow
markdown: https://topaz.ooo/docs/language/patterns-control-flow.md
covers: §5, §6, §10
# Patterns & Control Flow
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Use `if` for a single yes-or-no condition, `match` when the shape or category of a value determines the result, and `for` when performing the same work on each item. Patterns are constructs inside `match` and `for` that test data and bind names to required parts.
## Run one example
Save this as `data-control.tpz`:
```topaz
record StudyTask {
title: string,
minutes: int,
done: bool = false,
}
function pace(task: StudyTask) -> string {
if task.done {
return "done"
}
match task.minutes {
case 1..15 => "quick"
case 16..30 => "focused"
case _ => "deep"
}
}
let tasks: Array = [
StudyTask { title: "Run first program", minutes: 10, done: true },
StudyTask { title: "Build application", minutes: 25 },
]
let mut remaining = 0
for task in tasks {
if !task.done {
remaining = remaining + task.minutes
}
print("{task.title}: {pace(task)}")
}
print("remaining: {remaining} minutes")
```
Check it before running it:
```bash
topaz check data-control.tpz
topaz run data-control.tpz
```
The output follows array order:
```text output
Run first program: done
Build application: focused
remaining: 25 minutes
```
## Follow the decisions
The loop binds each array element to `task`. The inner `if` evaluates a single boolean condition to check whether the task is unfinished. It neither classifies multiple cases nor destructures the value.
When both outcomes require handling, write `if condition { ... } else { ... }`.
Compatible branch values allow the entire `if` expression to yield a value.
`pace` first uses `if` for the completed task case. Its `match` expression then evaluates `task.minutes` once and tests the `case` arms from top to bottom. `1..15` and `16..30` are range patterns. `_` serves as the catch-all pattern, ensuring every other integer produces a result.
| Choose | When it fits | What it produces |
| ------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `if` | A single condition selects between branches | A value when compatible branches yield values, or an effect-only statement otherwise |
| `match` | A value has several meaningful shapes or categories | The selected arm's value, along with any pattern bindings in that arm |
| `for` | Every item must be visited in iteration order | Statements executing side effects, or an `Array` of body results when used as an expression |
## Patterns select and bind
A literal or range pattern tests a value. An identifier such as `task` binds the matched value. Constructor patterns such as `Some(value)` and `Err(message)` destructure `Option` and `Result` values. Record patterns extract named fields.
Array patterns use `..` rather than call spread syntax. `case [head, ..tail] =>` binds the first element and assigns the remaining elements to `tail` as an `Array`. Use `_` when a fallback is required but the value is not needed.
`if let pattern = value { ... }` is useful for matching a single successful shape with an optional fallback. `while let` repeats the test until pattern matching fails. Their bindings are immutable and scoped exclusively to the successful block.
## Common mistake
Do not write `[head, ...tail]` inside a pattern. The `...` syntax is reserved for array and call spreads. The standard pattern for capturing remaining elements is `[head, ..tail]`.
Enum construction and enum pattern matching are intentionally distinct. Construct variants using the qualified enum name, such as `Status.Paused("review")`, but match variants without qualification, such as `case Paused(reason) =>`.
## Exact behavior that affects a choice
- A value-producing `if` or `match` requires compatible branch results.
- A closed enum match is verified for complete variant coverage. A guarded arm does not count toward that coverage.
- When complete match coverage cannot be established statically, an explicit `_` arm is required.
- Patterns evaluate from left to right. Bindings from a failed alternative do not leak into subsequent alternatives.
- A refutable pattern failure inside a collection comprehension produces an error rather than filtering items. Add an explicit comprehension `if` clause to skip items.
- `Array`, integer ranges, `Set`, and `map.keys` have a defined iteration order. Strings are not directly iterable.
- `while` loops and statement-position `for` loops produce no value. A `for` loop evaluated in expression position collects its body results in source order.
User-defined patterns, generators, user-defined iteration, exception-handling control structures, and labels on `while` or `for` loops are not supported in the current language.
Continue from the course example in [Data and Control](/en/docs/learn/data-control). See [Records & Nominal Data](/en/docs/language/records-nominal-data) for the shapes patterns can destructure, and [Collections & Comprehensions](/en/docs/language/collections-comprehensions) for iteration and filtering.
---
route: https://topaz.ooo/v5.20/en/docs/language/functions-generics
markdown: https://topaz.ooo/docs/language/functions-generics.md
covers: §3, §4, §5, §6, §7, §22
# Functions & Generics
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
A function defines a reusable transformation and specifies the types crossing its boundary. Start with a concrete named function. Use a lambda for concise behavior passed as a value, and introduce a type parameter only when a single relationship applies across multiple types.
## Run the progression in one program
Save this as `functions-generics.tpz`:
```topaz
record Label derives Show {
text: string,
}
function applyTwice(value: int, step: (int) -> int) -> int {
step(step(value))
}
function firstOr(values: Array, fallback: T) -> T {
match values {
case [] => fallback
case [first, ..] => first
}
}
function showValue(value: T) -> string {
Show.show(value)
}
let increment: (int) -> int = value => value + 1
let first: string = firstOr(["Topaz", "Classic"], "none")
let shown = showValue(Label { text: "docs" })
print("{applyTwice(3, increment)}")
print(first)
print(shown)
```
Check it before running:
```bash
topaz check functions-generics.tpz
topaz run functions-generics.tpz
```
The result is:
```text output
5
Topaz
Label { text: docs }
```
## Begin with a named function
`applyTwice` specifies two parameters and an `int` result. Its final expression provides that result, making an explicit `return` statement unnecessary. Use `return` when an early exit clarifies control flow.
The `step` parameter has type `(int) -> int`. Callers must provide a callable that accepts and returns an `int`. `increment` is a lambda matching that shape. A lambda uses `=>`, can be stored like any other value, and captures visible bindings under closure rules.
## Add a type parameter when the relationship repeats
`firstOr` does not need to know the concrete type of the array elements; it handles strings, integers, or any other type uniformly. It defines the relationship where the array elements, fallback value, and result all share the same type `T`. Because this call supplies a string array, type inference resolves `T = string`, so the caller does not need to specify a type argument explicitly.
This demonstrates rank-1 polymorphism. The named function is generic, and each invocation selects a concrete instantiation. Choose generics when the algorithm preserves a true type relationship; otherwise, choose a concrete type.
## Add a bound only when the body needs an operation
`showValue` calls `Show.show`, so its body requires proof that `T` conforms to the static `Show` protocol. `Label derives Show` provides that nominal conformance. The bound does not apply to arbitrary printable values. Structural values and primitives do not gain protocol conformance automatically.
Bounds represent an escalation beyond standard generics rather than the default. A named generic function may require an order-independent conjunction such as `T: Show + JSON` when its body genuinely uses both protocols.
## Choose the smallest function form
| Need | Choose | Signal to the reader |
| ----------------------------------------------------- | ------------------------------------ | ---------------------------------------------------------- |
| Reusable behavior with a stable name | A concrete named function | Parameters and result form a public contract. |
| Short behavior passed or stored as a value | A lambda | The surrounding context supplies concrete parameter types. |
| One algorithm preserving a relationship across types | A named generic without bounds | The same `T` connects inputs and result. |
| A generic body that calls a static protocol operation | A named generic with declared bounds | The bounds list the operations the body may require. |
## Calls, defaults, and variadics
Parameters are immutable. A default value is evaluated when its argument is omitted and must be a literal or `const` expression. It cannot reference another parameter. A variadic tail is written `...args: T`, must be final, and appears inside the function as `Array`.
A call evaluates its callee once and then evaluates positional and spread arguments from left to right. Named arguments follow positional and spread arguments. Array spread contributes only to a variadic tail. It does not fill an arbitrary fixed parameter.
Type inference is the standard generic call path. Where explicit type arguments are permitted, the list must be complete and agree with the value arguments and expected result. Partial decoration is a static error.
## Common mistake: reaching for a generic lambda
Anonymous behavior is a lambda whose parameter types become concrete from context. Type parameter bindings and protocol bounds belong to named function declarations. If behavior requires its own reusable generic contract, give it a name and declare that contract once.
## Exact limits
- Generic functions are rank-1. Higher-rank function values remain deferred.
- Lambdas, callable types, aliases, nominal declarations, receiver methods, and protocol methods do not declare bounds.
- Generic lambdas, default type parameters, variance annotations, dynamic protocol dispatch, and arbitrary trait constraints remain deferred.
- A generic body has exactly its declared bounds and cannot silently use a stronger operation.
For the initial function and lambda lesson, revisit [Values and Functions](/en/docs/learn/values-functions). Use [Types](/en/docs/language/types) for callable and generic type shapes, and [Bindings, Scope & Closures](/en/docs/language/bindings-scope-closures) for capture and lifetime.
---
route: https://topaz.ooo/v5.20/en/docs/language/records-nominal-data
markdown: https://topaz.ooo/docs/language/records-nominal-data.md
covers: §3, §5, §6, §8
# Records & Nominal Data
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Topaz offers several data forms because “these values have the same fields” and “these values represent the same domain concept” are different promises. Start with the distinction your program needs, then choose the smallest form that preserves it.
## Run one example
Save this as `records-nominal-data.tpz`:
```topaz
type DisplayValue = int | string
newtype UserId = int
enum Status {
Active,
Paused(string),
}
record User {
id: UserId,
name: string,
status: Status = Status.Active,
}
function statusLabel(status: Status) -> string {
match status {
case Active => "active"
case Paused(reason) => reason
}
}
function display(value: DisplayValue) -> string {
match value {
case number: int => "number {number}"
case text: string => text
}
}
let before: User = User { id: UserId(7), name: "Ada" }
let after: User = User { ...before, status: Status.Paused("review") }
let preview = { name: "Ada", age: 36 }
let older = preview{ age: 37 }
let ready = display("ready")
print("{before.id.value()}:{before.name}:{statusLabel(before.status)}")
print("{after.name}:{statusLabel(after.status)}")
print("{preview.age}:{older.age}")
print("{display(7)}:{ready}")
```
```bash
topaz check records-nominal-data.tpz
topaz run records-nominal-data.tpz
```
It prints:
```text output
7:Ada:active
Ada:review
36:37
number 7:ready
```
## Choose the data form
| Form | Use it when | Construction and observation |
| ----------------- | ----------------------------------------------- | ------------------------------------------------------------------- |
| Structural record | A local value only needs a known set of fields | `{ name: "Ada", age: 36 }`, then `value.field` |
| Nominal `record` | The domain name and declaration identity matter | `User { ... }`, fields, defaults, named update, and record patterns |
| `enum` | Exactly one member of a closed set is present | `Status.Active` or `Status.Paused(reason)`, select with `match` |
| Union `A \| B` | An existing value may have one of several types | Narrow it with literal, constructor, or type patterns |
| `newtype` | One base value needs a distinct domain identity | `UserId(7)`, then unwrap one layer with `.value()` |
`User` is a nominal record. Omitting `status` runs its default once, so `before` is active. `User { ...before, status: ... }` creates a fresh outer `User`, retains the other fields, and does not mutate `before` or rerun defaults.
`Status` is a closed enum. Construction uses the namespace (`Status.Paused`), while a match arm uses the unqualified variant (`case Paused(reason)`). The checker can therefore require that `statusLabel` handles every variant.
`DisplayValue` is a union alias, not a new runtime wrapper. The type patterns in `display` narrow the existing `int` or `string` and bind it. By contrast, `UserId` is a new nominal wrapper even though its base is `int`.
`preview` is structural. Its identity is its compatible field shape. Its update uses `preview{ age: 37 }`. This is a different operation from the nominal update used for `User`.
## Construct, update, and match
Nominal record fields are checked by name and type. Explicit fields evaluate from left to right. Missing defaults run afterward in declaration order. A nominal update allows one leading `...source`, then replacements. It copies only the outer record, so mutable values stored in fields remain shared.
The spread expression evaluates first and must supply the defining record identity and exact declared field inventory. Missing or duplicate fields produce runtime fault `GUARD_ARITY` (`TPZ5004`), while unknown fields produce `GUARD_NO_FIELD` (`TPZ5006`). These shape guards run immediately after the spread expression evaluates and before any replacement expressions evaluate. Explicit replacement fields then evaluate from left to right and take precedence over the base record's fields without re-evaluating declaration defaults.
A nominal record pattern such as `case User { name } =>` first checks that the value came from the `User` declaration, then reads the selected fields. Omitted fields are ignored, but the pattern must name at least one field. A structural record with the same fields is not a `User`.
## Common mistake
JavaScript-style `{ ...value }` is not a Topaz structural record update. Use `value{ field: replacement }` for a structural record and `Name { ...value, field: replacement }` for a nominal record.
Methods use a separate module-level `impl Name` block with immutable by-value `self`. Record bodies have no implicit `this`, `&self`, or `&mut self`.
## Exact boundaries
- Record, enum, and newtype declarations are module-top-level nominal declarations.
- Generic nominal parameters are invariant and need an expected type or another accepted inference context at construction.
- Nominal equality, ordering, and collection keys preserve declaration identity as well as compatible contents.
- Enum matches use bare variant names and are checked for complete coverage. Guarded arms do not establish coverage.
- Nominal pattern heads are bare names, not namespace-qualified names, and empty nominal patterns are invalid.
- A `newtype` constructor takes exactly one base value, and `.value()` unwraps exactly one layer.
Reflection, inheritance, row polymorphism, open nominal patterns, qualified construction, dynamic protocol dispatch, and Rust ownership-style receivers are not part of the current language.
Start with the course’s [Data and Control](/en/docs/learn/data-control) example. Use [Types](/en/docs/language/types) for aliases and unions, and [Patterns & Control Flow](/en/docs/language/patterns-control-flow) for exhaustive matching and bindings.
---
route: https://topaz.ooo/v5.20/en/docs/language/collections-comprehensions
markdown: https://topaz.ooo/docs/language/collections-comprehensions.md
covers: §5, §6, §9, §10
# Collections & Comprehensions
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Select a collection based on the behavior your application must preserve: positional order with duplicates, key-based lookup, or element uniqueness. Comprehensions provide a concise syntax to filter and transform these collections while preserving their ordering rules.
## Choose the collection first
| Collection | Choose it for | Observable behavior |
| ----------- | ----------------------------------------------------- | -------------------------------------------------------------------------- |
| `Array` | Ordered items, positional access, and repeated values | Iterates by increasing index and preserves duplicate values |
| `Map` | Values selected by key | Keys maintain insertion order; updating an existing key preserves its slot |
| `Set` | Unique values | The first matching value preserves the insertion slot |
## Run one example
Save this as `collections-comprehensions.tpz`:
```topaz
let scores = [10, 25, 10]
let labels = map { "Ada": 10, "Lin": 25 }
let unique = set { 10, 25, 10 }
let focused = [ for score in scores if score >= 20 => score * 2 ]
let first = scores[0]
let missing = scores.get(9)
let hasAda = "Ada" in labels.keys
let missingLabel = match missing {
case Some(value) => "found {value}"
case None => "missing"
}
print("{scores}:{unique.toArray()}:{labels.keys}")
print("{focused}:{first}:{hasAda}")
print(missingLabel)
```
```bash
topaz check collections-comprehensions.tpz
topaz run collections-comprehensions.tpz
```
It prints:
```text output
[10, 25, 10]:[10, 25]:[Ada, Lin]
[50]:10:true
missing
```
The array retains both `10` values. The set deduplicates the second `10` and preserves the initial insertion order. Because `labels.keys` returns an array ordered by insertion, membership checks are written as `"Ada" in labels.keys`.
## Read and change collections deliberately
`scores[0]` performs a direct indexed read. Passing a negative or out-of-range index triggers a runtime fault. `scores.get(9)` avoids runtime faults by returning an `Option`, which the example handles using `match`.
In-place mutation requires a mutable root binding. Declare `let mut values = [...]` before invoking a mutating operation such as `values.push(...)`. Conversely, a non-mutating expression can derive a new collection from an immutable binding.
Map duplicate handling differs from Set handling. Defining duplicate literal keys produces a static error. If two keys evaluate to equal values at runtime, map construction evaluates the operands and raises a fault rather than overwriting. `Map.insert` serves as the explicit operation for inserting or updating key-value pairs.
## Literal, loop, or comprehension?
- Use `[a, b]`, `set { a, b }`, or `map { key: value }` when elements are specified directly as literals.
- Use `for item in source { ... }` when expressing the logic as explicit sequential steps improves readability. In statement position, it executes side effects. In expression position, it collects body evaluation results into an `Array`.
- Use `[ for item in source if condition => result ]` when filtering and transformation fit naturally into a single expression. Replace the outer brackets with `set { ... }` for unique set elements or `map { ... }` for key-value entries.
Comprehension clauses start with `for`, continue with subsequent `for` or `if` keywords without commas, and conclude with `=>`. Clauses nest sequentially from left to right. The resulting collection is constructed only after every evaluated clause and body expression succeeds.
## Common mistakes
The collection types are `Array`, `Map`, and `Set`. `[T]` is not a valid collection type syntax. Set and map literals require explicit `set` and `map` keywords. `#{...}` and bare `{key: value}` syntax are invalid.
`key in map` is invalid. Use `key in map.keys` instead. A refutable pattern mismatch inside a comprehension causes a runtime fault rather than filtering items automatically. Write an explicit `if` clause to skip non-matching elements.
## Exact boundaries
- Array spread operations accept only another `Array`. Set and map literals and comprehensions do not support spread syntax.
- Empty `set {}` and `map {}` literals require explicit type context for `Set` or `Map`.
- Map keys and set elements must be recursively keyable.
- Removing an entry removes its insertion slot. Reinserting the entry appends it to the end. Updating an existing map value preserves key order.
- `for` loops iterate over `Array`, integer ranges, `Set`, and `map.keys`. Iterating over strings requires explicit scalar operations.
User-defined iteration, generators, lazy or parallel comprehensions, iterable spread, and unordered collection semantics are not supported in the current language specification.
Return to the course example in [Data and Control](/en/docs/learn/data-control). Refer to [Patterns & Control Flow](/en/docs/language/patterns-control-flow) for comprehension bindings, and consult [Collection Operations](/en/docs/stdlib/collections) for available standard methods.
---
route: https://topaz.ooo/v5.20/en/docs/language/pipelines-placeholders
markdown: https://topaz.ooo/docs/language/pipelines-placeholders.md
covers: §11
# Pipelines & Placeholders
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Use a pipeline when a result should visibly flow into the next operation.
`left |> right` evaluates `left` once, saves that value, and determines a single
well-defined way to supply it to `right`. A placeholder `_` marks the exact
call-argument position that receives the saved value.
## Start with a self-contained pipeline
Save this as `pipelines-placeholders.tpz`:
```topaz
function add(value: int, amount: int) -> int {
value + amount
}
function multiply(left: int, right: int) -> int {
left * right
}
function equal(left: int, right: int) -> bool {
left == right
}
let result = 5
|> add(2)
|> multiply(_, 3)
let repeated = result |> equal(_, _)
print("{result} {repeated}")
```
Check and run it:
```bash
topaz check pipelines-placeholders.tpz
topaz run pipelines-placeholders.tpz
```
The output is:
```text output
21 true
```
The first stage has no placeholder, inserting the saved `5` as the first
argument: `add(5, 2)`. The second stage explicitly specifies where the saved `7` goes:
`multiply(7, 3)`. In the final pipeline, both placeholders receive the same
saved `21`, making the call equivalent to `equal(21, 21)`. The left side is not
evaluated again for the second placeholder.
## Choose the right-hand shape
| Right side | Meaning |
| -------------------------- | --------------------------------------------------------------------------------------- |
| `value \|> call(other)` | Insert `value` before all explicit positional, spread, and named arguments |
| `value \|> call(_, other)` | Replace every valid `_` argument with `value` without inserting an extra first argument |
| `value \|> callable` | Call the callable with `value` as its single argument |
| `value \|> .field` | Read `field` from `value` |
Pipelines associate from left to right, passing the evaluated value of each stage to the next. The placeholder belongs to its nearest enclosing call. A `_` inside a pattern is instead a wildcard that discards a matched value. The two roles share a token but operate in separate scopes.
Multiple placeholders are valid when each appears in a call-argument position. They all receive the same saved left value. Explicit placeholder replacement takes priority. Once a valid `_` exists on the right, Topaz does not insert an extra first argument.
## Common correction
A bare `_` is not a value. It cannot be the entire pipeline right side, a callee, a member name, or a field label. Place it inside the argument list of the call that should receive the piped value.
Optional-property pipe sugar such as `value |> ?.field` is not supported. Use ordinary optional chaining before or inside an explicit lambda. Named arguments must still follow positional and spread arguments after pipeline binding. A right side that is neither a call, a callable value, nor `.field` is a static error.
Pipelines connect data flow directly. Use postfix `?` explicitly for Result propagation and `concurrent` with a declared timeout for concurrent work. Partial application, asynchronous lifting, and user-defined pipe operators remain separate language choices.
See [Operators & Expressions](/en/docs/language/operators-expressions) for precedence, [Functions & Generics](/en/docs/language/functions-generics) for callable values, and [Collections & Comprehensions](/en/docs/language/collections-comprehensions) for collection-producing stages.
---
route: https://topaz.ooo/v5.20/en/docs/language/null-option-result-faults
markdown: https://topaz.ooo/docs/language/null-option-result-faults.md
covers: §12, §13, §13a
# Null, Option, Result & Faults
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Use this guide when a value may be absent or an operation may fail. The primary
question is not “which operator is shortest?” but “can the caller continue execution?”
## Choose by meaning
| Situation | Use |
| ------------------------------------------------- | ------------------------------------------------------- |
| No value is an ordinary outcome inside Topaz | `Option` with `Some(value)` or `None` |
| External data explicitly carries a nullable field | `T \| null` |
| The caller can respond to a meaningful error | `Result` with `Ok(value)` or `Err(error)` |
| The program violated a runtime contract | A fault; evaluation aborts instead of producing a value |
Prefer `Option` for standard absence in Topaz APIs. Reserve `null` for data and
interop boundaries that explicitly contain it. Use `Result` when a failure
requires diagnostic details or a recovery decision.
## Turn bad input into a recoverable result
This runnable example converts the absence returned by `toInt` into a
descriptive `Result`:
```topaz
record StudyTask {
title: string,
minutes: int,
}
function parseMinutes(text: string) -> Result {
match toInt(text) {
case Some(value) if value > 0 => Ok(value)
case _ => Err("minutes must be a positive integer")
}
}
function makeTask(title: string, minuteText: string) -> Result {
let minutes = parseMinutes(minuteText)?
Ok(StudyTask { title: title, minutes: minutes })
}
function inspect(minuteText: string) -> () {
print("input: {minuteText}")
defer { print("inspection finished") }
match makeTask("Build application", minuteText) {
case Ok(task) => print("ready: {task.title}, {task.minutes} minutes")
case Err(message) => print("cannot add task: {message}")
}
}
inspect("25")
inspect("later")
```
Check and run the program:
```bash
topaz check failures-resources.tpz
topaz run failures-resources.tpz
```
```text output
input: 25
ready: Build application, 25 minutes
inspection finished
input: later
cannot add task: minutes must be a positive integer
inspection finished
```
`toInt` returns `Option` because non-integer text yields no parsed value.
`parseMinutes` determines that this absence is significant to its caller and
converts it into `Err`. In `makeTask`, the postfix `?` operator unwraps `Ok`.
Upon encountering `Err`, it propagates that error immediately from the returning
`Result` function. The final `match` expression handles both potential outcomes.
## Work with optional and nullable values
Optional access and fallback operators maintain this distinction:
- For `maybeUser: Option<{ name: string }>`,
`maybeUser?.name` evaluates to an `Option` result, and
`maybeUser?.name ?? "guest"` evaluates to a `string`.
- For `configPath: string | null`, `configPath ?? "default.toml"` evaluates the
fallback only when the value is `null`.
- `target ??= value` is a statement that performs assignment only when the target
is empty. An `Option` target requires `Some(42)` rather than a bare `42`. Topaz
does not wrap values in `Some` automatically.
Both `?.` and `??` evaluate their left operand once. `?.` also skips evaluating
method arguments when the receiver is empty. Each operator unwraps or flattens a single
optional layer without converting `Option` into `null`.
**Common correction:** Use standard `.` property access for non-optional and non-nullable
values. If a type combines `Option` and `null`, handle cases explicitly with pattern
matching rather than chaining optional operators.
## A fault is not an error value
This example requests an out-of-bounds array element:
```topaz
let tasks = ["Run first program", "Build application"]
print(tasks[2])
```
Running `topaz run intentional-fault.tpz` exits with a non-zero status:
```text output
error[TPZ4001]: index 2 is out of bounds for an array of length 2
--> intentional-fault.tpz:2:7
|
2 | print(tasks[2])
| ^^^^^^^^
```
The bounds violation constitutes a fault. It halts evaluation immediately and is neither
`None` nor `Err`. Postfix `?`, `??`, optional chaining, and `concurrent` cannot catch
or convert faults. Constant expressions containing invalid arithmetic may instead be
rejected during static checking prior to execution.
## Exact boundaries
- Postfix `?` is valid only within functions or closures whose return type can
represent the corresponding error value.
- Traditional `try` blocks, exception handling, fault catching, panic keywords, and automatic
retry mechanisms are not supported in Topaz.
- `assert(...)` is available only in explicitly selected `test-profile` builds.
It is not an assertion mechanism for general application logic.
- Resource cleanup on normal scope exit should use `defer`. Fault cleanup operates within a
narrower runtime scope, so faults must not be treated as recoverable `Result` values.
Continue with [Failures and Resources](/en/docs/learn/failures-resources) for
the guided exercise, [Defer & Resources](/en/docs/language/defer-resources) for
cleanup execution timing, and [Runtime Behavior](/en/docs/runtime/behavior) for the
runtime fault boundary model.
---
route: https://topaz.ooo/v5.20/en/docs/language/defer-resources
markdown: https://topaz.ooo/docs/language/defer-resources.md
covers: §1, §3, §4, §5, §6, §7, §9, §13, §13a, §14, §22
# Defer & Resources
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Use `defer` for general scope-exit cleanup. For one owned `File`, `using` combines acquisition, use in a child scope, and cleanup through an implicit close.
## Watch the cleanup order
Place a text file named `defer-resources.txt` beside the program. Its exact contents are:
```text
ready
```
Save this as `defer-resources.tpz`:
```topaz
function inspectNote(path: string) -> Result<(), string> {
using file = open(path)? {
defer { print("body cleanup") }
let note = file.read()?
print("note scalars: {note.scalars().length}")
}
print("resource scope closed")
Ok(())
}
match inspectNote("defer-resources.txt") {
case Ok(_) => print("done")
case Err(message) => print("error: {message}")
}
```
Check and run it:
```bash
topaz check defer-resources.tpz
topaz run defer-resources.tpz
```
The observed order is:
```text output
note scalars: 6
body cleanup
resource scope closed
done
```
`open(path)?` acquires the file once. Only after acquisition succeeds does `using` register its implicit close. The subsequent body `defer` therefore runs first, and the implicit close runs before execution reaches `resource scope closed`.
## Choose the form that owns the lifetime
| Situation | Use |
| ----------------------------------------------------------------- | --------------------------------------------------------------- |
| One cleanup call or block belongs to the current scope | `defer call` or `defer { ... }` |
| One acquired `File` belongs to one child block | `using file = open(path)? { ... }` |
| Acquisition, reading, or writing can fail under normal conditions | Return or match `Result`; use `?` only in a compatible function |
| The program has violated a runtime contract | Treat it as a fault, not as recoverable cleanup control |
Several `defer` statements in one scope run in last-in, first-out order. They also run when `return`, postfix `?`, `break`, or `continue` exits that scope. A `using` binding is immutable and visible only inside its body. A closure may retain the `File` value, but the implicit close still occurs when the `using` scope ends, so retaining it does not extend the resource lifetime.
## Result propagation is not a runtime fault
If `open` returns `Err`, no cleanup has been registered yet. If `file.read()` returns `Err`, postfix `?` exits the active `using` scope, so its registered cleanup runs before the error leaves `inspectNote`.
A runtime fault aborts evaluation immediately. Lexical `defer` statements and `using` cleanup after that point are unspecified. A fault raised by a deferred action follows the runtime logging or collection policy. It is not a catchable value and leaves an `Err` already being returned unchanged.
## Common correction
Do not spell the File operation as `File.open(...)`, and do not invent a general `using` protocol. The exact acquisition function is `open(path)`, and the exact statement accepts one bare immutable name and one `File`.
Explicitly calling `close()` inside a `using` body does not cancel the registered close attempt. Behavior after close or repeated close operations is not portable, so standard code should let one owner close the resource once.
## Exact boundary
Current Topaz resource behavior covers the `File`, `defer`, and `using` operations described above. User-defined disposal protocols, multi-resource acquisition, resource transfer, asynchronous cleanup, cancellation cleanup, host-exception cleanup, and module-lifetime finalization remain future decisions. Generated targets preserve the documented behavior or reject before artifact output. Check target-specific behavior on the intended target.
Review the guided failure model in [Failures and Resources](/en/docs/learn/failures-resources), compare absence and recoverable errors in [Null, Option, Result & Faults](/en/docs/language/null-option-result-faults), and see the exact File operations in [Files & Resources](/en/docs/stdlib/files-resources).
---
route: https://topaz.ooo/v5.20/en/docs/language/concurrency
markdown: https://topaz.ooo/docs/language/concurrency.md
covers: §2, §3, §4, §5, §7, §8, §10, §13a, §15, §19, §22
# Concurrency
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Use `concurrent` when independent expressions run concurrently and the caller requires a single named result record. Standard application code declares a duration and an explicit timeout fallback.
## Complete successfully
Save this as `concurrency-success.tpz`:
```topaz
let result = concurrent(timeout: 3s) {
total: 1 + 2
label: "ready"
} else {
{ total: -1, label: "timeout" }
}
print("{result.total} {result.label}")
```
Check and run it:
```bash
topaz check concurrency-success.tpz
topaz run concurrency-success.tpz
```
Both arms finish before the deadline, so their names become record fields:
```text output
3 ready
```
The record associates each value with its arm name. This does not indicate which arm started or completed first.
## Let the deadline choose the fallback
This second program has an arm that cannot finish. Save it as `concurrency-timeout.tpz`:
```topaz
function spin() -> int {
let mut i = 0
while true { i += 1 }
i
}
let result = concurrent(timeout: 5ms) {
stuck: spin()
} else {
{ stuck: -1 }
}
print("{result.stuck}")
```
Check and run it:
```bash
topaz check concurrency-timeout.tpz
topaz run concurrency-timeout.tpz
```
The deadline wins, no partial arm record is exposed, and the fallback supplies the entire result:
```text output
-1
```
## Distinguish the three outcomes
| What happens before the deadline? | Expression result |
| --------------------------------- | ---------------------------------------------------- |
| Every arm completes | A record with one field per arm |
| The timeout wins | The `else` block value |
| An arm faults | The whole expression faults, and `else` does not run |
An arm evaluating to `Err(error)` has not faulted. That `Result` is an ordinary value stored in its named record field. The fallback must be type-compatible with the successful arm record, which is why each example returns identical field names and compatible value types on both paths.
## Result names and scheduling
Arm names map results to record fields. Scheduler start order, completion order, output interleaving, fairness, and deterministic interleaving are unspecified. When output order matters, join the tasks first and then print the record.
After a timeout, runtime policy handles cancellation and resource cleanup. Later faults from abandoned work are not delivered to the Topaz expression. Code should remain independent of scheduler choice and post-timeout cleanup details.
## Common corrections
- Write `3s` or `5ms` with no space. Property forms such as `.seconds` are not duration syntax.
- A timeout duration scaled to milliseconds must fit unsigned `u64` (`0..=18446744073709551615`), with exact maxima `18446744073709551615ms`, `18446744073709551s`, and `307445734561825m`; larger values are rejected with `TPZ5001`.
- A timeout form requires `else`. A plain join cannot have `else`.
- `else` handles timeout only. It does not catch a fault or unwrap an `Err`.
- Arm names must be unique, and no syntax exposes a partial result.
The plain `concurrent { ... }` join is useful on an advanced concurrency page when waiting without a timeout is itself the point. It is not the default form for ordinary examples.
There is no implicit async model, `async`/`await` syntax, public task handle, general duration arithmetic, or specified cancellation protocol. See [Runtime Behavior](/en/docs/runtime/behavior) for observable fault and order boundaries and [Forbidden & Deferred Forms](/en/docs/language/forbidden-deferred) for familiar-looking forms that are not current Topaz.
---
route: https://topaz.ooo/v5.20/en/docs/language/strings-templates
markdown: https://topaz.ooo/docs/language/strings-templates.md
covers: §1, §16, §22
# Strings & Templates
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Use ordinary strings for text your program displays or compares. Use registry-tagged templates when values carry path, regular expression, shell, or SQL intent that requires structured representation. Template values carry structured tag intent alongside their content.
## Start with observable Unicode text
Save this as `strings-templates.tpz`:
```topaz
let title = "Topaz 💎"
let scalarCount = title.scalars().length
let configPath = p"config/topaz.toml"
let query = sql"SELECT name FROM projects WHERE title = {title}"
print("{title}: {scalarCount}")
```
Check and run it:
```bash
topaz check strings-templates.tpz
topaz run strings-templates.tpz
```
The output is:
```text output
Topaz 💎: 7
```
`title` contains seven Unicode scalar values: five letters, one space, and one emoji. The `scalars()` method returns an `Array` whose elements are single-scalar strings. This provides the standard method to inspect text positions. Storage bytes do not represent string positions, and Topaz performs no implicit Unicode normalization.
Because `configPath` uses the `p` template, it carries path-normalization intent. `query` uses the `sql` template, where `{title}` serves as a parameter boundary instead of direct SQL text insertion. The program omits printing both templates because template values are structured, non-renderable entities rather than ordinary display strings.
## Choose the form by the value you need
| Need | Form | Boundary |
| ----------------------- | ---------------------- | ------------------------------------------------------ |
| Display or compare text | `"Hello, {name}"` | Interpolation uses the standard value renderer |
| Multiline text | `"""..."""` | Closing-delimiter indentation controls exact stripping |
| A path template | `p"config/app.toml"` | Carries platform path-normalization intent |
| A regex template | `r"^[a-z]+$"` | Only the specified reduced-escaping floor is portable |
| Shell command data | `sh"tool {argument}"` | Construction does not execute a process |
| Parameterized SQL data | `sql"WHERE id = {id}"` | Interpolation becomes parameter metadata |
The `p`, `r`, `sh`, and `sql` tags work with ordinary or triple-quoted strings. The tag must directly precede the opening quote without intervening whitespace. With a space, `p "file"` parses as an identifier followed by a string rather than a call, producing a parse error.
Ordinary and triple-quoted strings support `{expression}` interpolation and the fixed escape set. When a newline immediately follows a triple-quoted opening delimiter, that initial newline is omitted. The exact spaces-and-tabs prefix preceding the closing delimiter must appear on each nonblank content line before Topaz removes it.
All tagged templates (`p`, `r`, `sh`, and `sql`) produce the built-in opaque type `template`. Use `template` in binding and return annotations, for example `let query: template = sql"..."` or `function query() -> template { ... }`. A `template` is distinct from `string`. Its `.tag` accessor returns the producing tag name as a `string`, and `.parts` returns the literal segments between interpolations as an `Array`.
## Common correction
Do not use `text[0]`, `text.length`, or slicing operations to inspect a string. Call `text.scalars()` and process the result using standard Array operations. This provides scalar access rather than grapheme-cluster segmentation.
Backticks, `${expression}`, single-quoted strings, `html"..."`, unrecognized adjacent tags, and user-defined tags are non-canonical. An `sh` template does not execute commands, and SQL interpolation does not permit direct text insertion into queries. HTML templates, user tag registration, grapheme-cluster APIs, direct string slicing, a fully fixed cross-platform regex dialect, and shell execution semantics fall outside this language surface.
A `template` value cannot be used as an ordinary `string`. Because `template` is a built-in type name, declarations named `record template`, `enum template`, or `newtype template` are rejected.
Refer to [Lexical Structure & Layout](/en/docs/language/lexical-layout) for quoting and newline rules, [Runtime Behavior](/en/docs/runtime/behavior) for rendering details, and [Forbidden & Deferred Forms](/en/docs/language/forbidden-deferred) for familiar syntax options that are intentionally unavailable.
---
route: https://topaz.ooo/v5.20/en/docs/language/modules-visibility
markdown: https://topaz.ooo/docs/language/modules-visibility.md
covers: §17
# Modules & Visibility
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Use modules when a file defines responsibilities required by another file.
Each `.tpz` file constitutes a single module, with no separate `module` declaration.
## Build one program from two files
The study-plan application uses the following source layout:
```text tree
study-plan/
├── topaz.toml
├── topaz.lock
└── src/
├── main.tpz
└── plan.tpz
```
`src/plan.tpz` defines the data model and reusable operations:
```topaz
export record StudyTask {
title: string,
minutes: int,
done: bool = false,
}
function parseMinutes(text: string) -> Result {
match toInt(text) {
case Some(value) if value > 0 => Ok(value)
case _ => Err("minutes must be a positive integer")
}
}
export function makeTask(
title: string,
minuteText: string,
done: bool = false,
) -> Result {
let minutes = parseMinutes(minuteText)?
Ok(StudyTask { title: title, minutes: minutes, done: done })
}
export function summarize(tasks: Array) -> string {
let mut remaining = 0
for task in tasks {
if !task.done {
remaining = remaining + task.minutes
}
}
"tasks={tasks.length}, remaining={remaining} minutes"
}
```
`src/main.tpz` imports the required three names:
```topaz
import src.plan { StudyTask, makeTask, summarize }
export function main(args: Array, stdin: string) -> Result {
let tasks: Array = [
makeTask("Run first program", "10", true)?,
makeTask("Build application", "25", false)?,
]
print(summarize(tasks))
Ok(0)
}
```
From the package root, check and execute the resolved unit:
```bash
topaz check --root . --locked
topaz run --root . --locked
```
```text output
tasks=2, remaining=25 minutes
```
Specifying an explicit root resolves `src.plan` to `src/plan.tpz`. The entry file and all targets reachable through its imports constitute the compilation unit. Unrelated files outside this closure are excluded from verification for this entry.
## Export only the boundary another module needs
Declarations are private by default unless prefixed with `export`.
`StudyTask`, `makeTask`, and `summarize` cross the module boundary.
`parseMinutes` remains an internal implementation detail; importing or accessing
it from `src/main.tpz` results in a static error. Imported bindings are read-only.
A selected import binds exported names directly:
| Goal | Canonical shape |
| ------------------------ | ------------------------------------------------ |
| Select a few names | `import src.plan { StudyTask, makeTask }` |
| Rename one selected name | `import src.plan { summarize as renderSummary }` |
| Bind a namespace | `import src.plan as plan` |
With the namespace form, exported values are accessed as
`plan.makeTask(...)`. A namespace acts as a compile-time lookup name rather than
a runtime value. A qualified exported type such as `plan.StudyTask` is valid in
type position, but `plan.StudyTask { ... }` is not a construction form. Select
`StudyTask` when direct nominal construction is needed.
**Common correction:** `import src.plan as plan { summarize }` does not combine
the two import forms. Choose either a namespace import or a selected import.
## Root, initialization, and package are different decisions
- A module path is a dotted address relative to the selected root, matching exact
file and directory names.
- Imports must be placed in the top-level import prologue, preceding other items.
- Dependencies initialize once, prior to the module that imports them. An
imported module may include declarations and bindings, but no free
runtime-bearing statements such as a top-level loop or assignment.
- `topaz.toml` specifies the package identity, entry point, dependencies, and build target
for the toolchain. It does not introduce a second module declaration syntax.
The package command can supply the entry point and root, as demonstrated above. When
specifying a file directly, the directory of the entry file serves as the default
root unless `--root` explicitly designates a containing root.
## Import cycles are rejected
Suppose `a.tpz` imports `b.tpz`:
```text
import b
export function fromA() -> int {
1
}
```
and `b.tpz` imports `a.tpz`:
```text
import a
export function fromB() -> int {
2
}
```
Executing `topaz check a.tpz --root .` reports the detected cycle:
```text output
error[TPZ3006]: import cycle: a -> b -> a
--> a.tpz:1:8
|
1 | import b
| ^
a.tpz: 1 diagnostic
```
Every import edge participates in cycle detection, even when an imported name is
used solely as a type. Resolve the cycle by moving shared declarations into a
third acyclic module or by assigning the dependency to one direction. No
exception exists for type-only cycles.
## Exact boundaries
The current module syntax does not include `use`, string or template paths, an
alias combined with a selection list, export lists, wildcard exports, re-exports,
export-site renaming, or side-effect-only imports. `export let mut` is also
rejected. Imports cannot reference paths outside the root, and namespaces cannot
expose private members.
Continue with [First Application](/en/docs/learn/first-application) for the
complete tested package, [Packages & Distribution](/en/docs/toolchain/packages)
for manifests and locks, and
[Forbidden & Deferred Forms](/en/docs/language/forbidden-deferred) for familiar
module constructs that Topaz deliberately does not accept.
---
route: https://topaz.ooo/v5.20/en/docs/language/forbidden-deferred
markdown: https://topaz.ooo/docs/language/forbidden-deferred.md
covers: §20
# Forbidden & Deferred Forms
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Refer to this guide when `topaz check` rejects syntax originating from JavaScript, Python, Rust, or an earlier Topaz draft. Topaz accepts the canonical forms below. Most familiar spellings have direct current replacements; the remaining feature families are listed as deferred.
## Check a program that uses the current forms
Save this as `forbidden-deferred.tpz`:
```topaz
let mut count = 0
let values: Array = [1, 2]
let transform: (int) -> int = value => value + 1
match values {
case [head, ..tail] => {
count = transform(head) + tail.length
}
case [] => {
count = 0
}
}
print("{count}")
```
Check and run it:
```bash
topaz check forbidden-deferred.tpz
topaz run forbidden-deferred.tpz
```
The output is:
```text output
3
```
This single program demonstrates current binding order, named collection types, callable types, lambdas, and list-rest patterns.
## Replace the spelling, not the meaning
| If you tried | Use instead |
| ------------------------------------- | ----------------------------------------------------------------------------- |
| `mut let count = 0` | `let mut count = 0` |
| `count++` or `count--` | `count = count + 1` or `count = count - 1` |
| `[T]` in a type | `Array` |
| `function(T) -> U` in a type | `(T) -> U` |
| `args: ...T` | `...args: T` |
| `[head, ...tail]` | `[head, ..tail]` |
| An anonymous `function (...) { ... }` | A lambda such as `value => value + 1` |
| Backticks or `${value}` | Double quotes and `{value}` interpolation |
| String indexing or `.length` | `text.scalars()` and Array operations |
| `async` or `await` | Explicit `concurrent` execution with a declared timeout where that model fits |
Module imports and exports follow fixed syntax rules. Use explicit member imports and inline export declarations from [Modules & Visibility](/en/docs/language/modules-visibility). Do not use `use`, string or template module paths, export lists, wildcard exports, re-exports, or export-site renaming.
Invalid spellings above are shown as text reference rather than valid Topaz examples. Run `topaz check` before selecting a backend target. The shared checker rejects these forms at the language boundary rather than asking a generated target to infer their meaning.
## Deferred forms and current alternatives
Some rejected forms have current replacements. Other features remain deferred because their semantics are outside the current language specification. Current code should use the documented alternatives; the listed feature families await a future language decision.
The unavailable feature families include grapheme-cluster and direct string-slicing syntax, HTML and user-defined template tags, broader or higher-rank generic constraints, recursive type aliases, broad protocol dispatch, package re-exports, general iterable spread, asynchronous cleanup, `async`/`await`, fault catching, panic/throw/catch keywords, and optional-property pipe syntax.
Rust ownership and receiver tokens such as `&self`, `&mut self`, `Vec`, and `use crate::` belong in labeled interop specifications. Standard Topaz uses module-level `impl Name`, built-in collection types, canonical imports, and its own record-update forms.
Start with [Syntax at a Glance](/en/docs/language-tour) for current forms. Use [Operators & Expressions](/en/docs/language/operators-expressions) for operator corrections and [Modules & Visibility](/en/docs/language/modules-visibility) for the exact program-structure surface.
---
route: https://topaz.ooo/v5.20/en/docs/stdlib/overview
markdown: https://topaz.ooo/docs/stdlib/overview.md
covers: §22
# Standard Library Overview
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
Start with the task rather than the API inventory. The standard library provides a cohesive, statically checked surface for values, collections, files, and host boundaries. Unrecognized names and members trigger checking errors rather than falling through to host-language methods.
## Choose a capability
| What you need to do | Start here | Failure shape |
| ----------------------------------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------- |
| Print text, convert a value, inspect Unicode, or transform values | [Core Library](/en/docs/stdlib/core) | A value, `Option`, or the helper's declared `Result` |
| Store ordered items, look up a key, or keep unique values | [Collection Operations](/en/docs/stdlib/collections) | Usually a value, `bool`, or `Option` |
| Read or write one whole file | `FS.readText`, `FS.writeText`, `FS.readBytes`, or `FS.writeBytes` | `Result` |
| Hold a file open across several operations | `open` with `using` or `defer` | `Result`, followed by lexical cleanup |
| Understand printing, numbers, order, or a runtime stop | [Runtime Behavior](/en/docs/runtime/behavior) | A documented value or a loud fault |
Choose `Array` for ordered elements, `Map` for key-value lookups, and `Set` for unique values.
Use `Option` when absence is an expected outcome, such as a missing map key. Use `Result` when external input or host effects can fail with a descriptive reason. A fault indicates that the running program violated a contract, whereas an invalid call or type is rejected statically.
## An everyday recipe
This program parses text and transforms an array. `toInt` uses `Option` because input text might not contain an integer. `map` returns a new array preserving the input order.
```topaz
let parsed: Option = toInt("42")
let values: Array = Array.of(1, 2, 3)
let doubled = map(values, value => value * 2)
print("{parsed}:{doubled}")
```
```text output
Some(42):[2, 4, 6]
```
`Some(42)` keeps the successful parse explicit. If the text does not represent an integer, the first part evaluates to `None`. The library does not substitute default values like `0` or raise catchable exceptions.
## How the surface is organized
Prelude functions and constructors including `print`, `toInt`, `Some`, `None`, `Ok`, `Err`, `map`, `filter`, and `reduce` require no imports. Built-in namespaces encompass focused module families such as `Math`, `Bytes`, `Hash`, `FS`, and `Path`. Package code may also reference the fixed `std.*` modules documented by the active toolchain. These represent checked exports rather than dynamic host namespaces.
The built-in namespaces available without an import are `Array`, `Map`, and `Set` for collections, `JSON`, `CSV`, `TOML`, and `URL` for data formats, `Bytes`, `ByteBuffer`, `Encoding`, `Codec`, `Hash`, and `Regex` for text and bytes, `Math`, `BigInt`, and `Decimal` for numbers, `Date` for calendar dates, `FS`, `Path`, and `Cli` for host access, and `Test` for assertions. Both compilers resolve these names the same way, and an imported `std.*` module with the same name shadows the built-in head inside that file.
The broader API surface covers encodings, hashes, paths, command-line parsing, regular expressions, CSV, TOML, JSON, URLs, dates, large integers, decimals, generators, parsers, and host capabilities declared by the selected profile. Open the focused page for exact signatures and target boundaries.
**Common correction:** Importing a standard module leaves filesystem, network, process, environment, clock, and database authority unchanged. Host effects run in profiles and environments that provide the corresponding capability.
## Exact boundaries
- String indexing uses Unicode scalar values unless an API explicitly specifies bytes.
- Collection iteration and snapshots preserve their documented order.
- Parsing and decoding preserve their declared `Option` or `Result` failure.
- Invalid arithmetic operations and contract violations remain loud.
- `Test.*`, free `assert`, and `std.test` are available in the test profile.
- Unknown direct names, virtual modules, and members produce static diagnostics.
## Continue by task
- [Core Library](/en/docs/stdlib/core)
- [Collection Operations](/en/docs/stdlib/collections)
- [Files & Resources](/en/docs/stdlib/files-resources)
- [Runtime Behavior](/en/docs/runtime/behavior)
---
route: https://topaz.ooo/v5.20/en/docs/stdlib/core
markdown: https://topaz.ooo/docs/stdlib/core.md
covers: §9, §22.1, §22.2, §22.5
# Core Library
This page describes Topaz v5.20. Before treating it as current, fetch https://topaz.ooo/version.json and compare its version field with 5.20. If it differs, discard the cached page and refetch it through https://topaz.ooo/llms.txt.
The core library handles common application tasks:
printing text, converting values, inspecting Unicode, representing absence or
failure, and transforming collections. Start here before reaching for a
specialized namespace.
## Choose the everyday helper
| Task | Use | Result |
| ---------------------------------------------- | ------------------------- | ---------------- |
| Print one line | `print("text")` | `()` |
| Parse an integer from text | `toInt(text)` | `Option` |
| Parse with a radix from 2 through 36 | `toIntRadix(text, radix)` | `Option` |
| Convert an `int` to a `float` | `toFloat(value)` | `float` |
| Construct a Unicode scalar from its code point | `fromCodePoint(value)` | `Option` |
| Inspect a string by Unicode scalar | `text.scalars()` | `Array` |
| Transform, select, or fold iterable values | `map`, `filter`, `reduce` | A new value |
`Some`, `None`, `Ok`, and `Err` are prelude constructors. An unannotated
`None`, `Ok`, `Err`, or empty collection requires an expected type when inference
cannot determine every type argument.
## Parse and transform values
This program retains failed parses as `None` and counts Korean text by
Unicode scalar count rather than byte count.
```topaz
function parseAll(values: Array) -> Array