Automatic Memory: How it stays fast
● Advanced6 min
Values look copied, but the compiler moves them, shares them and copies only when it must.
- Moved, not copied. When a value is used for the last time, it is handed over instead of copied.
- Shared and counted. A large value is shared inside, with a count of who holds it. The compiler removes most count updates while compiling.
- Copied only on change. A value is copied only when it is changed while someone else still holds it. If nobody else does, it is changed in place. So
text += partoritems.push(x)in a loop copies only what is added, not the whole value each time.
Step through what happens in memory, line by line:
The list is made once in memory. One variable holds it, so its count is 1.
Its first lines as a program you can run:
fn main() {
let a = [1, 2, 3]
let b = a
b.push(4)
print(a)
print(b)
}The program ran and printed:
kavach run values.kav
[1, 2, 3][1, 2, 3, 4]
Here b.push(4) is the moment the list is really copied: a still holds the old one.
Growing in a loop
Text built up in a loop has one holder, so each += adds to it in place. A copy taken before keeps its old value:
fn main() uses Console {
let report = ""
for i in 0..5000 {
report += "line {i}\n"
}
print(report.len())
let s = "ab"
const before = s
s += "c"
print(s)
print(before)
}The program ran and printed:
kavach run grows-in-place.kav
48890abcab
Numbers, text and the rest
Not every value is counted. Numbers stay where they are; text, lists and the rest are one object each:
| Values | Where they live |
|---|---|
Numbers, Bool, Char | In place: in a variable, or inside the value that holds them. Never counted, never freed on their own |
Text, Bytes, Decimal, BigInt, lists, maps, sets, records, tuples, unions, optionals of these, functions, interface values, Shared, tasks, channels, errors | One object each, which counts its holders and is freed when the count reaches zero |
A holder is anything that keeps a value: a variable, a list or record it is inside, a lambda that uses it, a task. A record a function never shares may never become an object at all: its fields stay in place, like numbers. A program can't tell the difference.
Nested lists and maps
The same holds at every depth. A list of lists, a list three deep, or a map of lists: change the copy anywhere inside, and only the copy changes:
fn main() {
let a = [[2, 3], [4, 5], [5]]
let b = a // shared: nothing copied yet
b[1].push(9) // b changes; a is still as it was
print(a)
print(b)
let deep = [[[2, 1, 4]], [[5]]]
let other = deep
other[0][0][1] = 99
print(deep)
print(other)
let shop: [String: [String]] = [:]
shop["fruit"] = ["apple", "pear"]
shop["veg"] = ["leek"]
let copy = shop
copy["veg"] = ["leek", "kale"]
print(shop)
print(copy)
}The program ran and printed:
kavach run nested.kav
[[2, 3], [4, 5], [5]][[2, 3], [4, 5, 9], [5]][[[2, 1, 4]], [[5]]][[[2, 99, 4]], [[5]]]["fruit": ["apple", "pear"], "veg": ["leek"]]["fruit": ["apple", "pear"], "veg": ["leek", "kale"]]
Only what changes is copied. When b[1].push(9) runs, the inner list [4, 5] is copied first, and 9 goes onto the copy. Then the outer list is copied, but only as a list of pointers to the same inner lists. [2, 3] and [5] stay shared by a and b. If nothing else held b, nothing would be copied at all: it would change in place.
A list holds one type, so [2, 3, [4, 5, [2, 1, 4]], 5], numbers and lists mixed, is refused:
fn main() {
const xs = [2, 3, [4, 5, [2, 1, 4]], 5]
print(xs)
}The compiler refused the program:
kavach run mixed.kav
error: expected `Int`, found `[Int]`: a list holds one type; to mix numbers and lists, use a union (2.6) or `Json` (2.12)To nest numbers and lists freely, name the two kinds of item in a union that holds a list of itself. Data from outside, such as JSON, comes as a Json value, which works the same way:
type Item = Num(n: Int) | Many(items: [Item])
fn total(item: Item) -> Int {
match item {
Item.Num(n) => n,
Item.Many(items) => items.sum(i => total(i)),
}
}
fn main() {
// [2, 3, [4, 5, [2, 1, 4]], 5]
const inner = Item.Many(items: [Item.Num(n: 2), Item.Num(n: 1), Item.Num(n: 4)])
const middle = Item.Many(items: [Item.Num(n: 4), Item.Num(n: 5), inner])
const xs = Item.Many(items: [Item.Num(n: 2), Item.Num(n: 3), middle, Item.Num(n: 5)])
print(total(xs))
}The program ran and printed:
kavach run nested-mixed.kav
26