Memory Model

Kestrel manages memory through ownership and scope-based drop. Every value has a single owner; when that owner goes out of scope, the compiler inserts a drop (calling deinit if one is defined) and frees the value.

You don't write free. You don't run a garbage collector. The compiler determines statically where each value's lifetime ends and inserts cleanup at that point.

Value semantics

Most Kestrel types are values — copying a struct copies its fields. Two Point variables that hold Point(x: 0, y: 0) are independent; mutating one doesn't affect the other.

Heap-backed types like Array, String, and Dictionary use internal reference counting for their storage, but they still present value semantics to your code through copy-on-write. You can reason about them the same way you reason about a plain struct.

struct Player { let name: String } struct PlayerList { var players: [Player] } var a = PlayerList(players: [Player(name: "Alice")]); var b = a; // copies the struct; players array storage is shared b.players.append(Player(name: "Bob")); // a.players is unchanged — COW copied the buffer before the write

Copy on write

Mutable collections in the stdlib (Array, Dictionary, String) are copy-on-write: a write triggers a copy of the underlying buffer only if another binding is sharing it. The result is value-like semantics — your writes never accidentally bleed into someone else's binding — without paying the cost of an eager deep copy on every assignment.

You don't usually have to think about this. The mental model is "structs and stdlib collections behave like values" and the runtime makes it true cheaply.

When deinit runs

deinit runs when the owning binding goes out of scope. That's deterministic — unlike a garbage collector, you can rely on cleanup happening at a predictable point in your code, which is what makes scope-based drop suitable for managing scarce resources (file handles, sockets, locks).

You can also call deinit variable; explicitly to release a resource before the end of its scope. See Structs → Deinitializers for how to write one.

Access modes

Parameters have an access mode that controls what a function can do with a value:

  • Read-only (default) — the function borrows the value; the caller keeps it.
  • mutating — the function can write through the binding; the caller sees changes after the call.
  • consuming — the function takes ownership; the caller can no longer use the value.

See Functions → Access Modes for details.