References

A reference is just what it sounds like: a reference to memory. C/C++ programmers can think of them mostly as pointers, except they must always be valid and arithmetic is not allowed.

Creating References

References are created using the & modifier:

fn main() {
    var charlie: Person("Charlie", 33) # Immutable
    var hillary: Person("Hillary", 68) # Immutable
    var barack: Person("Barack", 55) # Immutable
    var charef: &charlie

    charlie.age++
}

Unlike pointers in C/C++, references cannot refer to other references:

fn main() {
    var charlie: Person("Charlie", 33) # Immutable
    var hillary: Person("Hillary", 68) # Immutable
    var barack: Person("Barack", 55) # Immutable
    var charef: &charlie
    var charinception: &charef # Compile error!

    charlie.age++
}

This is because without pointer arithmetic and manual dereferencing (Ki dereferences automatically) there is no use for references to references.

Using References

References are very useful. Let's create a short example:

fn have_a_birthday(Person p): (Person) {
    p.age++
    return(p)
}

fn main() {
    var charlie: Person("Charlie", 33) # Immutable
    var hillary: Person("Hillary", 68) # Immutable
    var barack: Person("Barack", 55) # Immutable

    charlie = have_a_birthday(charlie)
}

This seems pretty straightforward, if somewhat contrived. What may be surprising, however, is that p is passed by value, meaning it is copied whenever have_a_birthday is called. Even worse, whenever have_a_birthday returns, it creates another copy. This is highly inefficient, especially for large types.

References can avoid these copies, however:

fn have_a_birthday(Person &p!) {
    p.age++
}

fn main() {
    var charlie: Person("Charlie", 33) # Immutable
    var hillary: Person("Hillary", 68) # Immutable
    var barack: Person("Barack", 55) # Immutable

    have_a_birthday(&charlie!)
}

Much better. Note that we no longer have to reassign charlie, as the memory is being mutated in-place.

Speaking of mutation, there are two types of references: immutable and mutable. Because have_a_birthday mutates its Person reference, we need to mark that parameter as mutable using ! and also pass it a mutable reference, again using !. Failure to do either of these will result in a compile-time error.

For more information on mutability, see here.

Guarantees

References play a key role in Ki's safety mechanisms, and they provide certain guarantees.

If A Mutable Reference To Memory Exists, No Other References To That Memory Exist.

Similar to Rust, Ki imposes this restriction to prevent data races: where one part of the code attempts to read a portion of memory while another part of the code attempts to write to it.

This also means that mutable references may not be copied. They can, however, be moved.

References Are Always Valid

This guarantee requires some constraints:

References May Not Be Stored In A struct Or type

Because the memory underneath the reference may be deallocated before the struct/type is, references may not be stored in them.

References May Not Be Passed To A gen Or async Function

This is for the same reason as the struct/type restriction, however, the &self reference is an exception.

Return Values May Not Be References

This is to prevent returning references to stack-allocated memory.

Moving Mutable References

It is sometimes helpful to pass mutable references from function to function. However, because there can only be one mutable reference to memory at a time, it cannot be copied (passed by value). It can also not be passed by reference, because we cannot take the reference of a reference. The only option left to us is to move the reference using *:

fn make_senior_citizen(Person &p!) {
    p.name = "Old " + p.name # Note that you'll get a **lot** of Old's...
}

fn have_a_birthday(Person &p!) {
    p.age++
    if (p.age > 30) {
        make_senior_citizen(*p)
        # Trying to use p after this point will result in a compiler error
    }
    # But using p again here is fine
}

fn main() {
    var charlie: Person("Charlie", 33) # Immutable
    var hillary: Person("Hillary", 68) # Immutable
    var barack:  Person("Barack",  55) # Immutable

    have_a_birthday(&charlie!)
}

Getting A Reference Back

Because you can either have many immutable references or a single mutable reference, it's important to be able to keep track of them. The various restrictions Ki places on references are designed to ensure that references can always travel back up in scope. The last example can serve as an example of this as well:

fn make_senior_citizen(Person &p!) {
    p.name = "Old " + p.name # Note that you'll get a **lot** of Old's...
}

fn have_a_birthday(Person &p!) {
    p.age++
    if (p.age > 30) {
        make_senior_citizen(p!) # Move p (&charlie) down into `make_senior_citizen`'s scope
        # Could use `p!` again here because `make_senior_citizen` no longer has
        # the mutable reference 
    }
}

fn main() {
    var charlie: Person("Charlie", 33) # Immutable
    var hillary: Person("Hillary", 68) # Immutable
    var barack:  Person("Barack",  55) # Immutable

    have_a_birthday(&charlie!) # Move &charlie down into `have_a_birthday`'s scope
}

This is essentially why references cannot be stored in structs or types, and why they cannot be passed to gen/async functions: allowing these would allow references to be trapped in different scopes, which requires much more complicated bookkeeping (Rust's borrow checker, for example) to verify.