Skip to content

Variables

NativeBrowserNode2 min read

Use variables to give values a name. Declare a value with let when it can change, or const when it should stay the same. Dream usually works out its type from the value you provide.

At a glance

let count = 0; // can change
const limit = 10; // stays the same
let name: string = "Ada"; // explicit type
  • let declares a value you can reassign; const declares one you cannot.
  • Dream infers the type from the initial value unless you write one.

Console examples elsewhere use System.println after import system; — see Imports. Short language snippets sometimes omit that import for brevity.

Declaring a variable

Use let. The type comes from the value on the right:

let x = 42; // int
let name = "Alice"; // string
let ratio = 3.14; // float
let done = false; // bool

Write the type when the value alone is ambiguous, or when you want a different type than inference would pick:

let score: double = 99.5d;
let items: int[] = [1, 2, 3];

Reassigning

let variables are mutable — assign a new value with =:

let count = 0;
count = count + 1;

Compound assignment (+=, -=, *=, /=, %=) and increment/decrement (++, --) also work. See Operators.

Constants

const declares a binding you cannot reassign. Trying to is a compile error:

const pi: int = 3;
// pi = 4;   // error: cannot assign to 'pi' because it is a const binding

Scope

A variable lives until the end of the block it was declared in. When a reference-typed value (string, array, class) leaves scope, Dream frees it if nothing else still uses it — see Memory Management.

fun main(): void {
    let a = 10;
    {
        let b = 20; // only alive inside these braces
        System.println(a + b);
    }
// b is gone here; a is still fine
}

Type inference rules

Inference reads the initializer. A few defaults to keep in mind:

  • Whole-number literals are int.
  • A literal with a . and no suffix is float; 3.14f is also float.
  • A d/D suffix makes a double (3.14d).
  • String literals are string.

If inference gives you the wrong type, add an annotation or a suffix:

let pi: double = 3.14159; // annotation
let pi2 = 3.14159d; // suffix

Top-level variables

let and const can also live at the top level of a file, outside any function or class. These become module globals: their initializers run once, in declaration order, when the module loads, and a later global may read an earlier one.

let counter: int = 10;
const FACTOR: int = 3;
let derived: int = counter * FACTOR; // may reference earlier globals

fun main(): void {
    counter = counter + 5; // top-level `let` is still mutable
    System.println(derived);
}

Visibility

Top-level variables are file-private by default: readable anywhere in their own .dream file, but not visible to files that import it and not exported. Two modifiers widen this:

  • public — importable from other files and exported to the WebAssembly host.
  • internal — importable from any other file that declares the same module path, but not exported to the host.
public let version: int = 1; // exported to the host

internal let budget: int = 64; // shared within `module app`, not exported
let cache: int = 0; // file-private (the default)

internal needs a declared module on both sides. A file with no module declaration has no module to share, so its internal globals stay file-private:

// lib.dream
module app;

internal let budget: int = 64;

// main.dream
module app; // same module, so `budget` resolves
import lib;

static is not a top-level variable modifier — it declares class members only. A top-level let is already a single value initialized once at module load.

// static let cache = 0;   // error: 'static' cannot modify a top-level variable