Mako has no garbage collector. Active memory/resource safety mechanisms include
ownership rules (hold/share) and region-based allocation (arena). This
guide explains when to use each strategy; generated C and FFI remain outside
the Mako type system.
Regular let bindings are the simplest form. They work like stack values:
let x = 42 // immutable
let mut y = 10 // mutable
y = y + 1
For most local computation, this is all you need.
hold marks a binding as move-semantics. Once moved, the original is gone:
hold let x = 7
hold let y = x // x is moved into y
print_int(y) // 7
// print_int(x) // COMPILE ERROR: use of moved value `x`
Moving into a function call also consumes the binding:
fn consume(n: int) -> int {
return n * 2
}
hold let val = 42
print_int(consume(val))
// print_int(val) // COMPILE ERROR: moved into consume
hold let mut x = 7
x = 9 // allowed -- still owned
print_int(x) // 9
Move individual fields while keeping the rest usable:
struct Point { x: int y: int }
hold let p = Point { x: 1, y: 2 }
let px = p.x // moves only x
print_int(p.y) // y still usable
// print_int(p.x) // COMPILE ERROR: x already moved
Integer and bool types are Copy -- they can be re-read after a hold binding
without consuming:
hold let n = 42
let a = n
let b = n // fine -- int is Copy
print_int(a + b) // 84
share creates a read-only shared reference. While a share exists, the
original cannot be mutated:
hold let a = 1
share let s = share_int(a)
print_int(share_get(s)) // 1
// a = 5 // COMPILE ERROR: cannot mutate while shared
share_drop(s)
// Now a is free again (if mut)
Rules enforced at compile time:
share let is always immutable (no share let mut)share_drop, block exit, or last use (NLL)| Situation | Use |
|---|---|
| Value has one owner, passed linearly | hold |
| Multiple readers, no mutation needed | share |
| Short-lived local computation | plain let |
| Large allocation, bounded lifetime | arena |
Prefer hold (unique ownership) whenever possible. It adds no reference-counting
traffic and gives the compiler maximum freedom to optimize.
An arena allocates many objects cheaply and frees them all at once when the scope exits:
fn main() {
arena a {
let msg = arena_text(a, "hello arena")
print(msg)
let xs = arena_ints(a, 4) // 4 ints, zeroed
print_int(len(xs)) // 4
let stamp = arena_stamp(a, 99)
print_int(stamp) // 99
}
// Everything in `a` freed here -- one deallocation for the whole region
print("arena done")
}
Inside an arena block, make allocates from the arena automatically:
arena a {
let mut s = make([]int, 3, 8) // backed by arena memory
s[0] = 10
s[1] = 20
s = append(s, 30) // grows within the arena
print_int(len(s)) // 4
}
struct Point { x: int y: int }
arena a {
let mut pts = make([]Point, 0, 4)
pts = append(pts, Point { x: 1, y: 2 })
pts = append(pts, Point { x: 3, y: 4 })
print_int(pts[0].x) // 1
}
The compiler tracks ownership through branches and loops:
hold let x = 7
if some_condition() {
let y = x // moves x on this branch
} else {
print_int(x) // x still alive on this branch
}
// x may or may not be moved here -- compiler tracks both paths
Moves in a loop iteration are checked per-iteration:
hold let x = 42
while condition() {
// Cannot move x here -- would be used-after-move on next iteration
print_int(x) // read of Copy type is fine
}
Use defer to ensure cleanup runs when a scope exits:
fn process() {
let fd = open_resource()
defer close_resource(fd)
// ... work with fd ...
// close_resource runs automatically on return
}
Defers execute in LIFO (last-in, first-out) order.
fn handle_request(fd: int) {
arena req {
let mut buf = make([]byte, 0, 4096)
let c = http_accept(fd)
if c < 0 { return }
let path = http_path(c)
let body = http_body(c)
if str_eq(path, "/data") {
let _ = http_respond_json(c, 200, body)
} else {
let _ = http_respond(c, 404, "not found\n")
}
let _ = http_close(c)
}
// All request memory freed in one shot
}
| Mechanism | Overhead | Lifetime | Use case |
|---|---|---|---|
let |
None (stack) | Lexical scope | Local computation |
hold |
None (move) | Until moved | Linear resource passing |
share |
RC seed | Until drop/end | Multiple readers |
arena |
Bump alloc | Block scope | Batch/request work |