Core Concepts

Topaz Data Types

Master the Topaz type system: primitives, collections, records, optionals, and literal types as locked in the Topaz v5.2 specification.

Library names note: Helper and member calls beyond the standard-library minimum (fileExists, readFileContent, fetch, .ok, .json, .status, toUpperCase, Date, etc.) are illustrative placeholders, not canonical APIs.

Topaz has a static type system with type inference, so code stays safe without getting verbose. The compiler infers types automatically when you don't specify them, but you can be explicit when needed.

Primitive Types

Integers and Floats

TOPAZ
// Integer
let age: int = 25
let bigCount: int = 9999999999

// Float
let price: float = 19.99
let pi: float = 3.141592653589793

// Type inference
let score = 95        // inferred int
let average = 87.5    // inferred float

Topaz primitives are int (64-bit signed), float (IEEE-754 binary64), string, bool, and (). The () is also an expression: the sole value of the unit type. Arbitrary-precision integers are not specified in Topaz v5.2, and int/float never mix implicitly in arithmetic.

Boolean

TOPAZ
let isActive = true
let hasAdminRights = false

// Used in conditionals
if isActive {
    print("Service is active")
}

Strings

TOPAZ
// String (single character or longer)
let grade: string = "A"
let emoji: string = "😀"
let name = "John Topaz"
let longSentence = "Welcome to Topaz — one way to say it!"

// String interpolation
let greeting = "Hello, {name}! Nice weather today."
print(greeting)  // "Hello, John Topaz! Nice weather today."

Single-quoted string and character literals ('A', '') are not canonical Topaz syntax. Use double-quoted strings for both single characters and longer text. Strings are not indexable and expose no .length; scalar-level access goes through s.scalars().

Collection Types

Arrays

TOPAZ
// Array declaration and initialization
let mut numbers: Array<int> = [1, 2, 3, 4, 5]
let names = ["Alice", "Bob", "Charlie"]  // inferred as Array<string>

// Array manipulation
let first = numbers[0]           // 1 — direct indexing faults when out of bounds
let safe = numbers.get(0)        // Some(1) — non-faulting read returns Option<int>
numbers.push(6)                  // [1, 2, 3, 4, 5, 6]
let length = numbers.length      // 6

// Functional style operations
let squares = map(numbers, x => x * x)
let evens = filter(numbers, x => x % 2 == 0)

Records

TOPAZ
// Record definition
let user = {
    name: "John Topaz",
    age: 28,
    email: "john@example.com",
    isActive: true
}

// Property access
print(user.name)               // "John Topaz"

// Immutable update with record literals
let calculator = { value: 0 }
let afterAdd = calculator{ value: calculator.value + 10 }
let afterMultiply = afterAdd{ value: afterAdd.value * 2 }

print("{afterMultiply.value}")       // 20
print("{calculator.value}")          // 0 (original unchanged)

// Reusable behavior can be written as regular functions
function addToCalculator(state: { value: int }, x: int) -> { value: int } {
    state{ value: state.value + x }
}

function multiplyCalculator(state: { value: int }, x: int) -> { value: int } {
    state{ value: state.value * x }
}

Advanced Types

Option Type

TOPAZ
// Option type for null safety
let searchResult: Option<string> = None
let username: Option<string> = Some("admin")

// Safe handling with pattern matching
match username {
    case Some(name) => print("Welcome, {name}!")
    case None => print("Login required")
}

// Safe handling with chaining (strings expose no .length;
// scalar count goes through s.scalars())
let nameLength = username?.scalars()?.length ?? 0
let display = username ?? "guest"

Result Type

TOPAZ
// Result type for error handling
function readFile(path: string) -> Result<string, string> {
    if fileExists(path) {
        Ok(readFileContent(path))
    } else {
        Err("File not found")
    }
}

// Safe error handling
match readFile("data.txt") {
    case Ok(content) => print("File content: {content}")
    case Err(errorMessage) => print("Error occurred: {errorMessage}")
}

Union Types

TOPAZ
// Type that can be one of several types
type ID = int | string
type Status = "pending" | "processing" | "completed" | "failed"

let userID: ID = 12345
let taskStatus: Status = "processing"

// Type-specific handling with pattern matching
function displayID(id: ID) -> string {
    match id {
        case numeric: int => "Numeric ID: {numeric}"
        case text: string => "String ID: {text}"
    }
}

Generic Type Use

TOPAZ
let numbers: Array<int> = [1, 2, 3]
let maybeName: Option<string> = Some("Alice")
let parsed: Result<User, string> = parseUser(rawJson)
let cache: Map<string, int> = Map.new()

Generic Functions and Aliases

Topaz supports rank-1 generic function declarations and generic type aliases. Type parameters are inferred at each call site; explicit call-site type arguments such as f<int>(x) are not part of Topaz v5.2. Pin a type with an annotated binding, parameter, or return position instead.

TOPAZ
function head<T>(xs: Array<T>) -> Option<T> {
    match xs {
        case [] => None
        case [first, ..] => Some(first)
    }
}

let first: Option<int> = head([1, 2, 3])

type Pair<T> = { first: T, second: T }
let bounds: Pair<int> = { first: 0, second: 100 }

Generic bounds, constraints, and interfaces remain deferred.

Type Aliases

TOPAZ
// Give simple names to complex types
type UserInfo = {
    name: string,
    age: int,
    email: string,
    permissions: Array<string>
}

type CallbackFunction = (string) -> ()

// Usage example
let admin: UserInfo = {
    name: "Admin User",
    age: 35,
    email: "admin@topaz.ooo",
    permissions: ["read", "write", "delete"]
}

Type Conversion

TOPAZ
let stringNumber = "123"

// toInt returns Option<int> — handle the None case explicitly
let parsed = match toInt(stringNumber) {
    case Some(value) => value
    case None => 0
}

The Topaz stdlib defines toInt(text: string) -> Option<int> as the canonical conversion helper, which never fails silently. Other conversion forms shown in legacy examples (float(...), string(...), tryInt(...)) are illustrative placeholders, not canonical APIs.

Advanced Patterns

Destructuring Assignment

TOPAZ
// Array destructuring
let [first, second, ..rest] = [1, 2, 3, 4, 5]
print("{first}")    // 1
print("{rest}")     // [3, 4, 5]

// Object destructuring
let { name, age } = user
print("Name: {name}, Age: {age}")

// Destructure inside the function body
function greet(user: { name: string, age: int }) {
    let { name, age } = user
    print("Hello, {name}! You are {age} years old.")
}

Type Guards

Topaz narrows types via match + TypePattern, not via is/as/any. any type and is/as operators are not canonical Topaz syntax. See the control-flow page for match-based narrowing examples.

Real-world Usage

TOPAZ
// Practical type definitions for real projects
type User = {
    id: int,
    name: string,
    email: string,
    createdAt: string
}

type UserResponse = {
    success: bool,
    data: Option<User>,
    error: Option<string>,
    statusCode: int
}

// User-fetching API
function getUser(id: int) -> UserResponse {
    let response = fetch("/api/users/{id}")
    
    if response.ok {
        let userData = response.json()
        return {
            success: true,
            data: Some(userData),
            error: None,
            statusCode: response.status
        }
    } else {
        return {
            success: false,
            data: None,
            error: Some("User not found"),
            statusCode: response.status
        }
    }
}

The type surface is small and predictable. Type inference keeps everyday code concise, and you can add precise annotations where you want the compiler to catch mistakes.