Initialization

Every binding holds a value before anything reads it. A let gets its value where it is declared. A var may be declared without one, and what happens then depends on its type:

var-declaration = "var" Name ":" type ";"
DeclarationWhen T …The variable starts as
var value: T;has an accessible constructor T()the value T() returns
var value: T;has no such constructornothing — it must be assigned before it is read
let value: T;—an error: immutable variable requires an initializer

There is no hidden default. A number declared without a value is not zero; it holds nothing until the program says what it is.

A default constructor runs

When the type has a constructor that takes no arguments, var value: T; calls it, and the variable is initialized from the start:

struct Tally {
    count: int32;
}

extend Tally {
    func Tally() -> Tally {
        return Tally { count: 10 };
    }
}
var tally: Tally;
tally.count += 1;
PrintLine("{}", tally.count);

This prints 11. The += reads count before changing it, and that read is fine: Tally() has already run.

Tracked storage

Without such a constructor, the variable holds nothing, and the compiler tracks it. Every path from the declaration to a read must assign it first:

func Grade(score: int32) -> char8[..] {
    var grade: char8[..];
    if score >= 90 {
        grade = "excellent";
    } else if score >= 50 {
        grade = "pass";
    } else {
        grade = "retry";
    }
    return grade;
}

Each branch assigns grade, so the return is accepted. Remove the final else, and a score below 50 reaches the return with nothing assigned:

error: value 'grade' may be uninitialized on some control-flow paths
  help: initialize or preserve 'grade' on every path before this use

A read with no assignment before it at all is reported directly:

var total: int32;
PrintLine("{}", total);
error: variable 'total' is used before it is initialized
  help: assign a value to 'total' before this use

One part at a time

A value of a plain type — one with nothing to destroy, such as a structure of numbers — may be filled in one part at a time: field by field, tuple element by tuple element. A part that has been written may be read at once. The whole value counts as initialized once every part has been written on every path that reaches the use:

var p: Point;
p.x = 1;
p.y = 2;
PrintLine("{} {}", p.x, p.y);

Before p.y is written, reading it — or using p whole — is refused, with a note that names what has been written:

error: variable 'p' is used before it is initialized
  note: only 'p.x' has been written, not 'p.y'

A fixed-size array of plain elements counts as initialized from its declaration, so a loop can fill it:

var values: int32[3];
for i in 0..3 {
    values[i] = (i as int32) * 10;
}

A value with a destructor is written whole

A type that needs destroying — one with a destructor, or with a field that has one — cannot be built a part at a time. A part written into a variable that holds no value would belong to no whole value, and nothing would ever destroy it:

var holder: Holder;
holder.handle <- Handle { code: 1 };
error: cannot write field 'handle' of 'holder', which holds no value
  help: initialize 'holder' whole, as in 'holder = Holder { ... }'

Assign such a variable whole — holder = Holder { … }; or holder <- other; — and from then on its parts can be replaced like any other place. Taking the address of a part, @holder.handle, is refused for the same reason.

Taking the address initializes

Taking the writable address of a whole uninitialized variable with @ counts as initializing it, because the address exists to be filled through. The compiler trusts the function that receives it to write a complete value:

func Fill(target: *var Point) {
    *target = Point { x: 3, y: 4 };
}
var q: Point;
Fill(@q);
PrintLine("{} {}", q.x, q.y);

This is how a variable is handed to a C function that fills it in — see FFI.

After a move

A variable that has been moved from holds nothing again, as if it had just been declared. Reading it is an error — for a variable named moved, error: value 'moved' is used after it was moved — until it is assigned whole once more.

See also