Learn Topaz

Coming From Other Languages

Map what you already know from Python, JavaScript, Java, or Rust onto the forms here, including the parts that are deliberately absent.

Outcome: translate the habits you already have into the forms used here, and know which habits have no counterpart on purpose.

Prerequisite: you can read code in at least one other language. No experience with this one is assumed.

Start from what transfers unchanged

Much of the surface will look familiar. Values, names, functions, conditionals, loops, and collections have recognizable roles, while the checker makes several important differences explicit. You should be able to sketch what the sample below does before learning every detail.

TOPAZ
record Task {
    title: string,
    minutes: int
}

function totalMinutes(tasks: Array<Task>) -> 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 say what this does. That is the point of starting here. The differences worth your attention are few, and they are below.

Four differences that actually matter

Mutation is opt in. A plain let name cannot be reassigned. Writing let mut states that reassignment is part of the design. Languages with readily available reassignment often make you inspect more of a function to learn 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, and the caller either handles it or passes it up with ?. The call boundary therefore carries the failure shape instead of requiring you to infer it from a body or exception convention. Runtime faults remain a separate, non-recoverable boundary.

Absence is in the type. Ordinary optional results use Option<T>. External nullable data can use T | null, and the two models stay distinct. Either way, the checker makes you handle the missing case before treating the value as present.

Host authority has an explicit boundary. File and I/O operations are available only through the selected host or product profile, while web products receive only their declared bounded capabilities. A function signature shows data and recoverable failure; imports, package configuration, and the selected profile complete the authority picture. Reading a Real File uses all of those clues.

What is deliberately absent

Habits with no counterpart here are worth knowing early, so you stop reaching for them.

There is no class hierarchy and no inheritance. Shapes are records and behavior lives in functions that take them. If your instinct is to model with a base class and overrides, model with records and explicit functions instead.

There is no reflection over your program at run time, no dynamic dispatch through a protocol table, and no row polymorphism. The exact deferred list is in Records & Nominal Data.

There is no implicit conversion between number kinds. An int does not become a float because the surrounding expression would be more convenient that way.

These boundaries are deliberate. They keep important choices visible in declarations and product configuration without pretending that a function signature alone describes every possible host interaction.

A translation table for the first hour

Your dict, Map, or HashMap maps to a collection, covered in Collections & Comprehensions. A data-only class often maps to a record. Start an interface-shaped boundary with explicit function signatures; use a static protocol when a supported generic contract needs one, without runtime dynamic dispatch. Recoverable try/except logic maps to a Result return. Ordinary optional results use Option; external nullable data may use T | null, and the checker keeps the two explicit.

Try this

You need a function that looks up a task by title and might not find one. Before reading the answer, decide what its result type should be.

Answer

An Option of a task, not a task. Not finding something is an ordinary outcome of a lookup rather than an error, so it belongs in the type as absence. A Result would be right if the lookup itself could go wrong, for example if it had to read a file first. Choosing between absence and failure is the judgment this language asks you to make explicitly, and making it once at the signature saves every caller from guessing.

Ready to continue when

You can name the four differences, say which habit from your previous language has no counterpart here, and choose between absence and failure for a function you are about to write.

Work through the course from First Program if you have not yet, or go straight to the forms in Syntax at a Glance.