Skip to content

Type Annotations and Optionals

Zolo infers the type of every binding from its initializer — use the let x: T = … annotation only when inference needs help (empty collections, ambiguous literals) or when explicit types aid readability:

Inference vs annotation; typed arrays and maps such as [int] and map.

04-type-annotations.zolo
Playground
// Feature: Explicit type annotation on bindings
// Syntax: `let name: Type = value`
// When to use: when inference is not enough (generics, ambiguous
// literals, optional nil types), or to keep the intent documented
// in the signature.

// Inference: the type comes from the RHS.
let inferred = 42  // int
let pi_inf = 3.14  // float
print(inferred)
print(pi_inf)

// Explicit annotation.
let count: int = 100
let ratio: float = 0.618
let label: str = "production"
let active: bool = true

print(count)
print(ratio)
print(label)
print(active)

// Optional type — `T?` allows `nil`.
let maybe_name: str? = nil
let maybe_age: int? = 30
print(maybe_name ?? "anonymous")
print(maybe_age ?? 0)

// Annotation on arrays and maps.
let scores: [int] = [10, 20, 30]
let prices: map = #{apple: 1.50, bread: 3.20}
print(scores)
print(prices)

// Useful when the RHS is generic/ambiguous (e.g. empty array).
let empty: [int] = []
print(empty)

When a value can legitimately not exist, declare the type as T?. Zolo allows nil only in optional types — the compiler rejects nil in bindings without ?. The ?? operator provides a default when the value is nil:

T? allows nil; ?? returns the right-hand side when the value is nil; if let extracts the present value.

10-nullable-init.zolo
Playground
// Feature: Bindings with optional type `T?` and nil initialization

// Syntax: `let x: Type? = nil`

// When to use: when the value can legitimately be absent — search

// result, optional field, parse that may fail. In Zolo, `nil` is

// only allowed in types marked `T?`.


// Optional initialized as nil — filled in later.

var current_user: str? = nil
print(current_user ?? "anonymous")  // anonymous


current_user = "Alice"
print(current_user ?? "anonymous")  // Alice


// Result of a function that may fail.

fn find_user(id: int) -> str? {
  if id == 1 { return "Alice" }
  if id == 2 { return "Bob" }
  return nil
}

let u1 = find_user(1)
let u3 = find_user(3)
print(u1 ?? "not found")  // Alice

print(u3 ?? "not found")  // not found


// Combining nullable + destructure (via `if let`).

if let name = find_user(2) {
  print("found {name}")  // found Bob

} else {
  print("missing")
}

// Default sentinel via `??`.

let port: int? = nil
let actual_port = port ?? 8080
print(actual_port)  // 8080

Challenge

Change let maybe_name: str? = nil to let maybe_name: str = nil and see the type error the compiler emits.

enespt-br