Variables

Variable blocks define variables; this is their definition:

variable {
    name: string
    mutable: 
    heap: 
    type: 
    value: {value initializer}
}

A string variable looks like this:

variable {
    name: "name"
    mutable: false
    heap: false
    type: string
    value: "Charlie"
}

You won't have to type all that out; Ki supplies a shorthand for variables:

fn main() {
    val name: "Charlie"
}

Ki has type inference, so it can figure out what type a variable is by looking at its initializer and its use. In this case, Ki knows that "name" is a string, because its initializer is a string literal.

If you'd rather not rely on that, you can specify the type manually. Primitives take a single value, so they're easy:

fn main() {
    val name1: "Charlie"
    val name2: "Charlie"
}

Besides type, "name" has two other attributes. First, it is allocated on the stack. If you want something allocated on the heap, just use the heap keyword:

fn main() {
    *name1: StringBuffer {
        value: "Charlie",
        alloc: 64
    }

    *name2: StringBuffer("Charlie", 64)
}

Note that we switched from a string to a StringBuffer here. Rarely will you want to allocate a primitive on the heap.

The other attribute is mutability. Variables in Ki are immutable by default; once you initialize a variable, you can't assign something else to it unless you've declared it mutable. Furthermore, even if a variable is a compound type with mutable fields, those fields can't be changed unless the variable is marked mutable as well. To mark a variable as mutable, use the var keyword:

fn main() {
    !name1: StringBuffer {
        value: "Charlie"
        alloc: 64
    }
    !name2: StringBuffer("Charlie", 64)
}

If you want both heap allocation and mutability, use both:

fn main() {
    *!name1: StringBuffer { "Charlie", 64 }
    *!name2: StringBuffer(value: "Charlie", alloc: 64)
}

Note that order is important. !* won't work.

We showed a little with StringBuffer, but here is more of how variable declaration works for compound types:

type Person {
    :fields {
        *first_name: ""
        *last_name: ""
        *favorite_color: ""
        age: uint(0)
    }
}

fn main() {
    charlie1: Person()

    charlie2: Person {}

    charlie3: Person {
        first_name: *"Charlie"
        last_name: *"Gunyon"
        favorite_color: *"green"
    }

    charlie4: !Person {*"Charlie", *"Gunyon", *"green", 32}

    charlie5: !Person(*"Charlie", *"Gunyon", *"green", 32)

    charlie6: *!Person {
        first_name: *"Charlie"
        last_name: *"Gunyon"
        favorite_color: *"green"
        age: 32
    }

    charlie7: *!Person {*"Charlie", *"Gunyon", *"green", 32}

    charlie8: *!Person(*"Charlie", *"Gunyon", *"green", 32)
}

Some things worth mentioning here:

  • Variables in Ki must be initialized, and this extends to type fields as well.
  • "Person" provides default values, which is how "charlie1" and "charlie2" can provide no initialization, and how "charlie3" can omit "age" from initialization. It is highly recommended that all fields initialize to a "zero" or "empty" value; this makes it very easy to work with higher level data structures, for example.
  • You can use parentheses if everything fits on the same line.
  • If you don't use named parameters, you must specify parameters in order.