Docs
Memory
Managed values are owned by scopes and disposed when control leaves those scopes. Echo has no tracing garbage collector.
Garbage collection
Echo has no tracing garbage collector. Mark-and-sweep pauses and concurrent heap walkers are outside the language model. Pure reference counting is outside the user-facing model as well.
Reclamation is scope-owned: every managed allocation has an owning lexical or dynamic scope. When control leaves that scope, values still owned by it are disposed on that edge, at a known program point.
Scope-owned dispose
Reason about lifetimes through program structure. A block, function body, or other structured region owns the managed values created inside it. Leaving the scope (including ^ return, < break, and > continue) releases what that scope still owns.
/ std/io
/ std/str
$ show = () {
$ xs = [1, 2, 3]
io.print(str.from_int(xs[0]))
; when show returns, values still owned by the body are released
}
show()When a value must outlive its creating scope (returned, stored into a longer-lived struct or list, and similar cases), ownership is promoted outward first. Unpromoted owners are released when their scope ends.
Escape and graph promotion
Promotion is graph-based: when a managed value escapes a scope, every reachable managed allocation still owned by that scope is promoted with it (list elements, struct fields, nested products). Allocations already owned by a longer-lived scope stay where they are.
/ std/io
/ std/str
$ make = () {
$ xs = [7]
$ holder = [xs]
^ holder
}
$ r = make()
io.print(str.from_int(r[0][0]))
; nested list survives make's frame via graph promotionThis is region ownership with graph evacuation. Shared longer-lived values that a nested structure only points at stay put; only allocations owned by the escaping scope move, so they are neither stolen nor double-freed.
Values, references, and ownership
Copy rules and dispose rules are different questions. Assignment always copies the binding: structs and lists share the object; numbers, strings, and other value kinds copy the value. See /docs/values for copy behavior.
Sharing storage does not invent a second free policy. Ownership for dispose stays scope-based: one owning scope is responsible for release, and graph promotion moves that responsibility for the whole escaping subgraph when a value escapes.
Working model
You reason about lifetimes with ordinary program structure: nested blocks ending, functions returning, loop bodies finishing an iteration. Under the product model, managed heap is released on those edges at known program points, without waiting for process exit or a background collector.
The language law is fixed: scope-owned dispose with graph promotion on escape. The toolchain implements registries, graph promote, and dispose on leave-scope edges.