Variables

Variables are a fundamental part of any programming language. Here's how you work with them in Ki.

TODO: Build a chart mapping literals to types

Basics

Ki requires the programmer to initialize all variables with a value, including compound types. While this may sound onerous at first, in practice little work is required to satisfy this requirement, and there are ways to reduce the workload besides. For instance, types allow default values for their fields, and allow for static "constructor" methods (Java programmers would call them "factory functions") to quickly utilize the most common initializations.

This requirement is found in many C style guides as well, however C is problematic in that ANSI C (C89) only accepts variable declarations at the top of functions. Consequently programmers tend to write code like this:

void safe_func_one(Enemy *e) {
    Player *p = NULL;

    if (!e) {
        return;
    }

    p = enemy_get_last_target(e);

    if (!p) {
        return;
    }

    player_run(p);
}

Ki (and C99) allow the programmer to write code without a frivolous and unnecessary pre-initialization:

void safe_func_one(Enemy *e) {
    if (!e) {
        return;
    }

    Player *p = enemy_get_last_target(e);

    if (!p) {
        return;
    }

    player_run(p);
}

Or, a Ki version:

fn safe_func_one(e: &Enemy) {
    # Ki doesn't allow NULL pointers, so there's no need for a check here
    with (p: e.get_last_target()) {
        p.run()
    }
}

Declaring Variables

Variable declarations in functions look like this:

fn main() {
    var name1: "Charlie"

    echo("Hello {{name1}}!")
}

There's a lot packed into that single declaration, so let's break it down:

Allocation

In the above example, "name1" is allocated on the stack. Here's how to allocate it on the heap:

fn main() {
    var name1: *"Charlie"

    echo("Hello ${name1}!")
}

For more information on Ki's memory management, see memory model.

Types

Ki is strongly typed, but it can infer the type of a variable based on what's assigned to it. For example, in Ki one can define a range type, which is a ranged integer:

range MonthOfYear [1..12]

fn main() {
    var month: 12

    echo("This month is number {{month}}")
}

In this small example, "12" is a potentially valid value for a host of integer types, including our range "MonthOfYear". However, in this situation Ki will use int instead of MonthOfYear as we would prefer (because the type better conforms to its use). So let's be a little more specific:

range MonthOfYear [1..12]

fn main() {
    var month: MonthOfYear(12)

    echo("This month is number ${month}")
}

Some more exmaples:

fn main() {
    # This is a stack variable
    var age: u8(62)
    # This is a unique pointer to a heap-allocated variable
    var name: *string("Charlie")
    # This is an atomic shared pointer to a heap-allocated variable
    var celtics: $Team("Celtics")
    # This is a non-atomic shared pointer to a heap-allocated variable
    var pacers: ^Team("Celtics")
    # This is a weak pointer to a heap-allocated variable
    var also_celtics: %celtics
    # This is a reference to a heap-allocated variable
    var nameref: &name
}