Skip to content

Variables

A variable is declared as <type> <name> = <initializer>:

int age = 25
float rate = 1.5
bool active = true
string name = "Pluvial"
auto count = 10 # inferred as int
auto title = "Rain" # inferred as string
  • The initializer is required — there is no default initialization.
  • With an explicit type, the initializer’s static type must match exactly. int x = 1.5 is a compile error (no implicit conversion).

auto infers the variable’s type from the initializer and then fixes it permanently. auto is not dynamic typing:

auto x = 10
x = 20 # OK
x = "hello" # compile error — the type is fixed as int

auto a = null is a compile error: null alone does not determine an element type. Write int? a = null instead.

  • Reassignment is allowed only when the type matches.
  • Assignment is a statement, not an expressiony = x = 1 is invalid.
  • A block { } introduces a new lexical scope. Variables declared inside a block are not visible outside it.
  • Redeclaring a name in the same scope is an error.
  • Shadowing is allowed in nested scopes: an inner block may declare a new variable with the same name as one in an outer scope. The inner one is a distinct new variable that covers the outer one; leaving the block restores the outer variable unchanged. Name resolution searches from the innermost scope outward (innermost-wins).

At runtime, top-level variables live in a globals array, while block-local variables live on the value stack (frame-relative slots). The garbage collector marks the entire value stack as a root, so locals are protected automatically.

The operators += -= *= /= %= **= &= |= ^= <<= >>= are pure syntactic sugar: x op= e lowers to x = x op e. The base operator’s type rules and error messages apply unchanged — int += float is a compile error, string += int is an error, and bitwise forms are int-only.

There are three legal left-hand sides:

  1. A variablex += 1. The readonly check is identical to plain =, so for-loop variables, match bindings, and self cannot be assigned even with an augmented operator ("cannot assign to for-loop variable 'x'", and so on).
  2. An array elementa[i] += 1. The receiver and index are evaluated once, so a[get_idx()] += 100 calls get_idx() exactly once.
  3. A struct fieldobj.field += 1, including nested paths such as o.inner.v += 1.

Not currently supported (deferred):

  • Augmented assignment on a map index — m[k] op= e — because a map read returns V? and needs narrowing.
  • Chained forms — x += y += 1.