Outcome: Connect runtime values to checked types and place a repeated calculation inside a named function.
Prerequisite: Complete First Program and be able to run topaz check before topaz run.
Extend the working file
Create values-functions.tpz:
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:
topaz check values-functions.tpz
topaz run values-functions.tpzThe expected output is:
Topaz basics: 25 minutes left
sessions: 1Read 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 and Functions & Generics. Generics serve as reference material for later sections and are not a prerequisite. Proceed to Data and Control.