Docs/Data & ownership

Memory & references

Understand copies, shared object identity, and read-only or writable views.

On this page

Valk manages ordinary class and collection allocations with garbage collection. Most application code does not pair every allocation with a free. You still need to understand whether an expression copies a value or refers to existing storage.

Understand assignment#

Valk
class Counter {
    value: int (0)
}

fn main() {
    let counter = Counter{}
    let same_counter = counter
    same_counter.value++
    println(counter.value) // 1
}

Both variables name the same object. Reassigning one variable would change what that variable refers to; changing the object's property is visible through both references.

Structs and fixed arrays are value types: assignment copies their fields or elements. Copying a field that holds a reference still copies the reference, not the referenced object.

Borrow a sequence#

Use a slice borrow when a function needs to read existing elements without constructing another container:

Valk
fn total(values: &[int]) int {
    let result = 0
    each values as value {
        result += value
    }
    return result
}

fn main() {
    let scores = Array[int]{ 10, 20, 30 }
    println(total(scores.view())) // 60
}

&[int] is a read-only view of integer elements. Passing an array view to this function does not copy its elements. Use &mut [int] when the function must write to the supplied storage.

Valk
fn reset(values: &mut [int]) {
    let index: uint = 0
    while index < values.length {
        values[index] = 0
        index++
    }
}

fn main() {
    let scores = [int]{ 10, 20, 30 }
    reset(scores)
    println(scores[0]) // 0
}

The mut is part of the API contract: callers can see that a function may change their elements. A read-only borrow cannot be used to write or to call methods that mutate the borrowed data.

Distinguish a view from a copy#

values[start .. length] makes a copy. &values[start .. length] asks the collection for a view. With a writable array, the view can modify the same storage:

Valk
fn main() {
    let scores = Array[int]{ 10, 20, 30 }
    let middle = &scores[1 .. 1]
    middle[0] = 99
    println(scores[1]) // 99
}

A view has its own bounds. Do not treat an existing view as a live window into future array growth: the array may move to new storage while the view retains the storage it was created from.

For a single value, &T and &mut T express the same distinction between reading and writing. For example, use a mutable borrow of an array's struct element when you need to update the element in place rather than a copied struct value.

Close resources explicitly#

Garbage collection manages memory; it is not a scheduling mechanism for releasing a file, socket, or lock. Close external resources when the operation finishes, often with defer.

A class can define a gc_free() finalizer for cleanup during collection, but the time at which collection happens is not the point at which a local variable leaves scope. Keep timing-sensitive cleanup explicit.

For data used across threads, ordinary references are not the whole story. Read Concurrency for shared values and Lock[T]. Raw pointers and manual allocations belong in Native interoperability.