# Memory model

URL: /docs/tour/memory
Markdown: /docs/tour/memory.md

> Value semantics, var rebinding, compiler-inserted reference counting with Perceus reuse, cycles through Graph, scope-bound resources, and backends in Sakura Lang.

> **Pre-alpha**
>
> The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates.

The compiler owns memory. There are no lifetimes, no borrow annotations, no `Rc`/`Arc`, no pointers, and no reference cells in user code. The price is a restricted data model: no aliasable mutation, no cycles in the heap, and explicit scopes for resources.

## Value semantics [#value-semantics]

Every value behaves as an independent value. Assigning, passing, capturing, and returning never create an observable alias. Records, variants, lists, maps, and strings are deeply immutable from the program's point of view. Physical sharing is an optimization the program cannot detect.

`let` bindings are immutable. A `var` binding can be **reassigned**, which changes the binding and nothing else:

```sakura
type User = { id: Int, name: String, handle: String }

fn rename(u: User, name: String) -> User {
  var v = u
  v.name = name          // sugar for v = User { ..v, name: name }
  v
}

test "updates never alias" {
  let a = User { id: 1, name: "Ada", handle: "ada" }
  let b = rename(a, "Ada L.")
  expect_eq(a.name, "Ada")
  expect_eq(b.name, "Ada L.")
}
```

Collections grow the same way: `xs = List.push(xs, 1)`. `List.push` returns a new list, so calling it as a statement and dropping the result is error `V2040`, which catches the forgotten reassignment:

```sakura
fn evens(n: Int) -> List<Int> {
  var xs: List<Int> = []
  for i in 0..n {
    if i % 2 == 0 { xs = List.push(xs, i) }   // List.push(xs, i) alone would be V2040
  }
  xs
}
```

## Closures capture values [#closures-capture-values]

A closure captures `var` bindings **by value at creation**. It cannot assign to an outer `var` (`V2041`); accumulate with a `var` in the same function body, or with `List.fold`. A `try` body follows the same rule: it may stop at any failing call, so it cannot assign to a `var` declared outside it either (`V2041`). Assign after the `try` instead: `let v = try { … } catch { … }` and then `total = total + v`.

```sakura
test "closures see the value at creation" {
  var greeting = "hello"
  let say = fn() { greeting }
  greeting = "bye"
  expect_eq(say(), "hello")
}
```

The only stateful constructs are handler `var` state and `Graph`, and both are confined. Shared mutable state, such as a counter or a cache, lives in a handler instance:

```sakura
effect Counter { fn next() -> Int }

fn counter() -> Handler<Counter> {
  handler Counter {
    var n = 0
    fn next() { n = n + 1; n }
  }
}
```

The state is private to the instance, initialized once per `with`, and released when the `with` exits. See [Handlers](/docs/tour/handlers).

## Reference counting and reuse [#reference-counting-and-reuse]

With no aliasable mutation and strict evaluation, the heap is an acyclic graph: a value can only reference values that existed before it. That makes reference counting complete without a cycle collector. The plan for native and WASM ([design §8.2](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)):

- **Non-atomic counts per task.** The compiler inserts `dup`/`drop` after Core lowering (Perceus). Values sent across worker threads are promoted to atomic counts on send.
- **Same-constructor reuse.** When a value is uniquely referenced, an update or a `match` that rebuilds a value of the same shape reuses its memory.
- **Copy-on-write buffers** for `List`, `Map`, and `String`. A `var xs` whose last use is `List.push(xs, v)` appends in place when the count is one, which gives amortized O(1) appends without exposing uniqueness in the language.

```sakura
type Tree = Leaf | Node(Tree, Int, Tree)

fn increment(t: Tree) -> Tree {
  match t {
    Leaf => Leaf
    Node(l, v, r) => Node(increment(l), v + 1, increment(r))
  }
}
```

If the caller passes its last reference to `t`, native code can rebuild each `Node` in the cell it just took apart. If the tree is still used elsewhere, the update copies and the original stays intact. Either way the program's behavior is identical; reuse is never observable.

When an update in a loop is forced to copy because an older value is still live, the compiler emits warning `W8001 copy_on_write`, pointing at the use that retains it.

The JavaScript backend ignores all of this and uses the host's garbage collector. Semantics are the same on every backend.

## Cycles: use Graph [#cycles-use-graph]

Reference counting cannot free cycles, and Sakura Lang's data model cannot build them. Cyclic structures use `std/graph`: a `Graph<N>` owns a table of nodes, and edges are `NodeId`s (slot plus generation) validated against the graph. There are no weak pointers in v0.

```sakura
import std/graph.{Graph, NodeId}

type Person = { name: String }

fn befriend(g: Graph<Person>, a: Person, b: Person) -> (Graph<Person>, NodeId, NodeId) {
  let (g1, ia) = Graph.insert(g, a)
  let (g2, ib) = Graph.insert(g1, b)
  (Graph.connect(Graph.connect(g2, ia, ib), ib, ia), ia, ib)
}
```

`ia` and `ib` refer to each other through the graph, but no value points to itself. `Graph.get(g, id)` returns `Option<N>`, so a stale id is a `None`, not a dangling pointer.

## Resources [#resources]

Files, sockets, database connections, HTTP bodies, and fibers are **resource types**: external types, declared without a body, whose representation belongs to the host. They are affine (used at most once as a whole), are not `Eq`, `Show`, or `Schema`, and are bound to a scope provided by their effect:

```sakura
effect Fs {
  fn with_file<T, e>(path: Path, mode: Mode, body: fn(File) -> T ! e) -> T ! {Fail<FsError> | e}
}
```

The excerpt above is from the std `Fs` declaration ([design §10.2](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)). `Sql.transaction(fn() { … })` follows the same pattern. The closure's result may not contain the resource (`V6003`, the same escape check that keeps capabilities in scope). Resources are released on normal exit, `fail`, defect, and cancellation, in reverse order of acquisition. A failed release is logged as a defect, never ignored.

## Regions [#regions]

Not in v0. The `region` keyword is reserved. Request-scoped arenas may arrive with the native backend as an internal optimization first, and become user-visible only if measurements justify it.

## Concurrency and determinism [#concurrency-and-determinism]

There is no shared mutable memory, so tasks cannot race on data. Tasks communicate through their results (`Task.join`, `Task.all`, `Task.race`) and through handler state, whose clauses run one at a time per instance. There is no channel type in v0. Evaluation is strict and left to right, arguments before the call, and `Map`/`Set` iterate in insertion order on every backend. See [Concurrency](/docs/tour/concurrency).

## Backends [#backends]

| Backend | Milestone | Memory management |
| --- | --- | --- |
| JavaScript (ES modules, trampolined CPS) | first, M0 onward | host GC |
| WebAssembly (`wasm-encoder`, runtime in linear memory) | M4 | non-atomic RC, no WASM GC required |
| Native (Cranelift) | M5 | Perceus RC with reuse |

In v0 the only build target is JavaScript:

```bash
sakura build --target js
```

## Costs and limits [#costs-and-limits]

- Reference counting costs something on every `dup` and `drop`. Reuse is what makes it competitive; how well it works is unmeasured.
- Sharing a large value and then updating it copies. `W8001` points at loops where this happens.
- Cyclic data needs `Graph` and `NodeId`s instead of direct references.
- On the JS backend, `Int` arithmetic and literals beyond ±(2^53−1) are defect `D003` until a wider representation lands. Parsing is the exception: `String.parse_int` returns `None` for such a value, because parsing is where untrusted input enters.
- Native handler state uses a per-instance lock. Whether that holds up under parallelism is an open question ([design §16](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)).

## What the compiler checks [#what-the-compiler-checks]

- There is no syntax for mutation of shared values or pointer identity, so aliasing bugs cannot be written.
- A dropped non-`Unit` result is an error (`V2040`), which catches `List.push(xs, v)` without reassignment.
- Neither a lambda nor a `try` body can assign an outer `var` (`V2041`).
- A resource cannot escape its scope (`V6003`).
- Reference counting is inserted automatically and is never visible in source. Reuse is an optimization only.

The planned WASM runtime in M4 uses conservative reference counting with explicit ownership. Borrow inference and in-place Perceus reuse remain M5; current execution uses JavaScript GC.

## Next [#next]

[Concurrency](/docs/tour/concurrency): structured tasks, cancellation, timeouts, and retries.
