A summary of Mere's memory-management strategies, how mere currently handles them, and what's planned. Deeper design notes live in separate internal notes.
| Strategy | When is memory freed? | Examples | Strengths | Weaknesses |
|---|---|---|---|---|
| Manual (malloc/free) | The programmer calls free each time |
C | Maximum control | Frequent use-after-free / leaks |
| GC | A runtime garbage collector decides | OCaml, Java, Go, Python | Safe, easy | Pause times, memory overhead, poor for real-time |
| Ownership (move + borrow) | Automatically at scope exit | Rust | Compile-time guarantees, zero cost | Cyclic / self-references are painful; learning curve |
| Region (bulk per-region) | The whole region scope is freed at once | Cone, Vale, Cyclone, ML/Talpin research | Fast alloc / bulk free; cyclic refs OK | Requires designing region lifetimes |
| Stack-only | At stack-frame exit | C autos, Rust let |
Zero cost | Strict size/lifetime constraints |
Mere is designed to let you mix and match: the programmer chooses a strategy explicitly, and the compiler verifies safety.
Based on the design note 01_memory_model.md:
A value has one owner; freed when the owner leaves scope. Equivalent to Rust's T.
let x: owned String = String.from("hello")
let y: owned String = x // move; x is no longer usable
Pass a reference without transferring ownership. Equivalent to Rust's &T / &mut T, except Mere plans to refine the borrow annotations (&shared write etc.; design Q-004).
Long-lived state that is periodically compacted — an interpreter's heap, a
server's session table — does not fit region R { }: its lifetimes nest
(LIFO), and a compaction needs the NEW generation to outlive the OLD one. The
loop is the hand-over-hand version: each iteration runs in a fresh arena named
R; the body sees x : option C (None on entry, Some carry after) and answers
region_flow[C, D] (prelude): Continue carry deep-copies the carry into the
next arena — containers included, via the __mdeep family — and releases the
current one; Done d copies d out and exits.
The escape rule splits where the block's could not: C may mention R (a
Map[R, ...] carried from arena to arena is the construct's point), D may
not — same check as a block's result. No fresh rigid variable per iteration
is needed: nothing of arena N reaches arena N+1 except through the Continue
copy, so one name R honestly denotes "the loop's current arena" throughout.
Not carryable (refused at emit time, by type): functions (their captures hide
behind a void* the deep copier cannot see into), Channel, ThreadHandle,
OwnedVec, StrBuf, ByteBuf.
Measured on the C backend: 2.4 GB of dead map-entry overwrites churned through 2000 cycles, live set 51 entries — peak footprint 1.6 MiB, 0.32 s. Backends: interp + C; llvm / wasm / rv32 refuse cleanly (their reclamation is a LIFO bump rollback, which cannot express the swap).
Values placed in region R are freed all together when R is destroyed. Bump-allocator backed. Only Trivial types (no Drop) can live in a region. Unified with arena under Q-008.
A "bundle type" built inside a region: immutable, non-moving. Lets you express self-references without unsafe.
view DocumentView[R] of Document {
own: &R Document,
tokens: &R [&R str], // points into own.text
}
Guarantees the value lives only on the stack. No heap allocation.
Areas where Rust's ownership struggles:
- Self-referential structs (
Pin<T>+unsafe) - Graphs / cycles (escape into Rc/RefCell)
- Bulk short-lived allocations (overhead of individual frees)
- async + lifetimes (lifetimes threaded across function calls)
Region resolves these via bulk-free per region.
region R {
// R is a bump allocator (just a pointer + size)
let a = R.alloc(Node {...}) // one pointer bump
let b = R.alloc(Node {...}) // ditto
a.next = b // references freely valid inside the region
b.next = a // cycle is fine
// ... computation ...
} // The whole region's memory is freed at once — no individual destructors
- Parsers / compilers: one input = one region; AST is built, then freed in bulk.
- One frame of a game: per-frame region; everything dropped at frame end.
- Request handlers: one request = one region; freed after response.
- Transactions: a region per transaction boundary.
Only "types without Drop" (Trivial) can be placed in a region. Why: bulk free without invoking individual destructors. Types that have Drop (DB connections, file handles, etc.) are managed separately via with (Q-011 resolved):
with db = Database.connect(...) in
region R {
let nodes = ... // many allocations into R
process(db, nodes)
}
// R is destroyed (Trivial only; no destructors)
// db.drop() runs (it has Drop, so it's released individually)
The "one request = one region" use case above is no longer aspirational — as of v0.1.31 it is the implemented semantics on the C backend:
-
Value allocations follow the current region. Strings, cons cells, and variant nodes allocate in a thread-local current region. A
region R { ... }block makes itself current for its body, so everything the body allocates is reclaimed when the block exits. Container structs (Vec / Map / StrBuf) stay in their own binding region — they carry identity and must not die with a scratch block. -
A container a FUNCTION builds follows the region its CALLER decided (v0.1.464, C backend). A container's region lives in its type, and a function's body used to allocate through the region variable in its own scheme, which the call site never bound: the type said the caller's block, the value went to the default region. The region is now passed IN, as leading
__lang_region*arguments on the uncurried__directtwin, and a call site hands over what it bound — a block it is inside, one of its own region parameters (which is how a chain reaches a body three calls down), or the default region where nothing decided.What this is NOT is the callee asking what region is current at run time. That was tried in v0.1.453 and is unsound for a chain of calls, because the body and the call site hold different copies of the variable; m3d could not render a second frame, and v0.1.456 withdrew it. Passing it in makes the type and the value say the same thing, so carrying the result out of the block stays the type error it already was.
Per backend: the C backend passes it. The LLVM backend does not yet — it has no uncurried twin to hang the argument on, so its
region_reclaim_checkleg asserts the old behaviour and goes red when it is taught. Wasm needs nothing: it has one bump for every region, and what keeps a callee's allocation alive there is the high-water mark (Q-132), not a region argument. RV32I needs nothing either: it does not reclaim regions at all. Those two are not "unimplemented" — there is no argument for them to pass. -
Closure environments follow the current region too (v0.1.290). They used to be allocated in the default (program-lifetime) region for one reason: a closure's env is a type-erased
void*, so nothing could copy it out of a dying block, and a permanent allocation was the only safe answer. A closure now carries a third member besideenvandfn— acopyfunction generated per env type, which region-allocates a fresh env and deep-copies each captured field through that field's own__mcopy_<tag>.__mcopyfor an arrow calls it, so a closure leaves a region block the way every other value does. A closure with no captures (env == NULL) and an FFI adapter holding a borrowed pointer leavecopyzero and are copied shallowly, exactly as before. -
The copier lives on the ENV, not in the closure (v0.1.292). Every env struct starts with
{__lang_region* __r; void* (*__copy)(region*, void*);}, and__mcopyfor an arrow reads that header through thevoid*it already has. So the closure struct stays{env, fn}— two pointers, for every program. That matters because a closure is passed by value through every frame of an interpreter's dispatch: a third pointer per frame took a 512 MB-stack interpreter over the edge on a deeply recursive program, andldcaps-stack_sizeat 512 MB on arm64, so there is nothing to buy it back with. The__rfield also makes copying idempotent: copying an env into the region it is already in returns it unchanged, so a chain of envs capturing closures does not copy without bound, and a shared env keeps its identity instead of becoming two mutable states. An FFI adapter's env is a real struct too — a header plus the borrowed pointer, in the default region, with__copy = NULLmeaning "do not copy me". One field answers both questions, and every closure with a non-NULL env can be asked. -
The block's result is copied out into the enclosing region (per-type deep copy, specialized like the
show/==derive family), so returning a value from a block is always safe. A container cannot be a block result — the typer's region-escape check rejects it. -
Containers own their contents (v0.1.30 copy-on-store):
map_setdeep-copies the key and value into the map's own region;vec_push/vec_setcopy the element. A stored value therefore outlives whoever stored it, no matter where it was allocated. Strings are immutable, so the copies are unobservable.- Overwriting a heap element repeatedly leaks the old copies. Because
the container's region is bump-allocated,
vec_set/map_setcopying a new string into a slot that already held one cannot reclaim the old copy — so a hot loop that overwrites the same slot with fresh strings grows O(writes), not O(1) (measured: 4Mvec_setof a fresh string into index 0 ≈ 550 MB). Scalar (int/bool) elements are unaffected (their "copy" is a no-op). Eliding the copy for a value already resident in the container's region needs type-level region tracking onstr(deferred); until then, prefer scalar slots for hot-overwrite loops, or a fresh per-batch region. (map_seton an existing key copies only the value, v0.1.77, so a counter keyed by repeated strings stays O(distinct keys).)
- Overwriting a heap element repeatedly leaks the old copies. Because
the container's region is bump-allocated,
-
Channel payloads copy through a per-message region:
channel_senddeep-copies into a malloc-backed message region;channel_recvcopies out into the receiver's current region and frees the message. A sender's scratch region can die while the message is in flight. -
Measured effect: an idiomatic line-at-a-time counter with a per-line region runs at constant ~1.5 MB RSS over 8M lines (previously 246 MB); a Redis-wire KV server with per-command regions holds flat RSS under sustained load.
-
Ready-made streaming combinators.
contrib/streampackages the per-line region loop so a tool does not have to hand-roll it:import "contrib/stream/stream.mere"; Stream.each_line path cb(side effect per line) andStream.count_lines path predkeep peak memory at O(longest line) — measured 1.4 MB vs 61.7 MB for the naive loop on a 57 MB file. The callback must not let the line escape (storing it into an outer container deep-copies it out and reintroduces O(file) growth); this is the ergonomic path a streaming line tool wants.
Backend note: the interpreter is GC-backed (same value semantics, memory behaviour trivially fine).
The LLVM backend gained the same semantics in v0.1.443. It had none of it
before: every value allocation named @__lang_default_region whatever region
blocks were around it, region R { } was a stack alloca that only explicitly
region-typed things went into, and there was no per-type copy-out because
nothing it held needed copying out. On test/regionreclaim/pertree.mere -- a
tree built inside a region block, only a scalar leaving it -- 100 iterations at
depth 16 cost 2.5 MB on C and 316 MB on LLVM; it is 5.8 MB now, and flat in
the iteration count like C's.
Both printed the same answer throughout, which is why the parity suite never
saw it: parity compares what a program prints, and this was a difference in
what it held. scripts/region_reclaim_check.sh is the gate that does see it,
and it was written to fail either way -- it pinned the gap while the gap
existed, went red when v0.1.443 closed it, and now pins the reclamation.
Two things the LLVM backend refuses as region-block results, both by the same rule the C backend uses: a container (identity -- a copy would be a different object) and a function (its captured environment lives in the block, and this backend has no environment copier). A result whose type never resolved is not copied at all, because the only values that reach a region boundary without a concrete type are the shared nullary nodes of a boxed variant, which live outside every arena.
The Wasm backend reclaims region blocks as of v0.1.37 (mark on the value stack + result copy-out via $__mcopy_<tag>;
boxed results -- lists, tuples, records, variants, floats, bytes -- copy out
correctly since v0.1.418, before which only scalar results assembled),
with one honest difference from C: there is no per-container storage, and
therefore no copy-on-store. Until v0.1.458 the answer to that was to make
escaping stores compile errors. It was the wrong answer twice over — the
same store performed by a callee went through and corrupted memory, and the
exemption for unboxed elements let the container's reallocated buffer dangle
instead of the element (Q-132) — because a guard that has to be right about
every store in the program cannot be syntactic.
The answer now is a high-water mark. A store into a container that predates
the innermost open block raises $__lang_hwm to the current bump, and the
block's exit restores max(its mark, hwm) instead of its mark. Sound because
the value stored, and any buffer the store reallocated, were allocated before
that point and lie below the bump; conservative because the block keeps its
other garbage too — which is what the C backend does with a __heap value
anyway, namely never free it. It costs one global compare when no block is
open, and nothing at all for a block that touches only its own containers.
What is still refused inside a region block is what a mark cannot help with:
channel_send, spawn, and closure-registering externs. Those hand the value
to another thread or to the host, on no schedule ordered with the block's exit.
Measured: the playground 2048 with a per-move region holds its bump pointer constant across 30,000 moves.
The default (program-lifetime) arena grows by chaining blocks rather
than aborting when it fills: when an allocation would overrun the current
block, a geometrically larger block is malloc'd and chained on, and blocks
never move so existing pointers stay valid. The C backend gained this in
v0.1.25 (a long-running server exhausted the old fixed-cap arena); the LLVM
backend gained the same bounds-check-and-grow in v0.1.82 — before it, its
__lang_region_alloc was a pure bump with no bounds check, so an
allocation-heavy program (e.g. a per-pixel renderer) silently overran the
4 MB arena and crashed while interp/C/Wasm agreed. Guarded by
test/parity/region_growth.mere (a checksum over ~6 MB of live region
allocations, matching across all four backends).
Containers grow in place when they can (v0.1.414). vec_push,
strbuf_push and bytebuf_push double their buffer when it fills. In a bump
arena the old buffer cannot be handed back, so before v0.1.414 a container
built by pushing cost twice its final size in cumulative allocation -- the
matmul benchmark allocated 12.6 MB for three 2 MiB matrices, to within 248
bytes of exactly 3 x (2 + 2) MiB. Now, when the buffer being grown is the most
recent allocation in its region and the current block has room, the bump
pointer simply moves and the buffer keeps its address; when a large buffer
lives in a dedicated block of its own (the v0.1.307 policy for allocations
bigger than a quarter of the bump block), that block is realloc'd and the
chain re-linked; otherwise the growth is a fresh allocation plus a copy, as
before. The program cannot observe which path ran -- only alloc_total and the
footprint change (matmul 12.6 -> 6.3 MB; a 200,000-int Vec 4 -> 2 MiB, which
scripts/region_slack_check.sh now holds). The C backend does all three; the
LLVM backend does the in-place path for Vec; the Wasm backend does it for
Vec only while no region block is open, because its blocks are a rollback
of the one bump pointer and extending an outer container's buffer into a
block would be undone with it. A Map still copies its arrays on growth.
Lists can be built in order (v0.1.416). ListBuf[R, T] (lb_new /
lb_push / lb_to_list, stdlib reference) appends by splicing the previous
cell's tail, so a list produced first-to-last costs one cell per element
instead of two -- the reversed accumulator an immutable list otherwise needs
was 23 MB of the JSON benchmark's 105 MB. Its cells reference the pushed
values without copying, which is sound because a push is refused while a
different region is current (and after lb_to_list has handed the cells
out); the builder itself is a container and obeys the escape rules above.
The "Phase 2" section below is a record of the first implementation slices (the region/view syntax layer). Phase 11 → 31 implemented the 4 borrow modes (&R T / &mut R T / &shared write R T / &exclusive R T) + borrow checker + with Drop integration + the 4 Q-010 collections (Vec / OwnedVec / StrBuf / Map) + 4-backend codegen (interp + C / LLVM / Wasm) parity. Details: language-reference.md §3 region/view/with / codegen.md §4.
Added in Phase 36 (2026-06-22): a narrow value restriction in the typer. let v = map_get m k in ...-style let-binds through mutable containers are no longer generalized (so 'a doesn't leak). Details:
is_value edecides whether an expression is in value form (literal / fn / Var / Tuple of values / etc.).ty_mentions_mutable_container tdecides whether a type containsOwnedVec[T]/Map[R, K, V]/StrBuf[R].- Generalization is suppressed only when both fail (non-value AND contains a mutable container).
This is a narrowed variant of ML's standard value restriction (limited to types involving mutable containers) — ordinary fn lets like let inc = fn x -> x + 1 remain polymorphic.
Added in Phase 38.G-1 (2026-06-22): automatic scope-bound Drop for let v = owned_vec_new () in body (Level 1). Implements N1 of the N1/N2/N3 decomposition from the 39_nll_linear_design.md design notes:
- If body doesn't let v escape lexically,
free(v->data)is auto-emitted at scope end (same shape as the Phase 15.13with). - Static analysis:
no_value_leak v body(v doesn't appear inside Tuple / Constr / Record_lit / Fun) +tail_does_not_return_v v body(body's tail expression's type doesn't include OwnedVec). - If both pass → auto-Drop; if either fails → fall back to the existing registry + main-end sweep (safe-by-default).
- Implemented in C + LLVM; Wasm uses a bump-arena scheme that doesn't need per-allocation free.
- Level 2 (NLL Light: drop at last use) and Level 3 (Full Linear: static use-after-move detection) remain deferred (see DEFERRED §1.3).
This is a compromise from Mere's "explicitness > brevity" philosophy: explicit with is still supported and recommended (for custom Drop types), and the typical OwnedVec pattern (build → query → return scalar) gets auto-Drop.
What works (Phase 2: syntax + value expressions + escape check + view declarations + region-enforced construction + field-access region propagation)
region R { body }— introduces R as a region name in scope.&R T— region-tagged reference type.&R v— region-tagged value expression.- Escape check — if R leaks into the type of
region R { body }'s body, it's a compile error. view V[R] of T { fields }declarations.- View region enforcement (Phase 2.3) — view construction allowed only inside a region block.
- Type-level region tag on view values + field-access region propagation (Phase 2.4) — a view value's type carries the construction-time region as
Name[R]; field accesses / record updates substitute it with the actual region. The view value itself is subject to escape checking (cannot leave the region).
> region R { 42 }
- : int = 42
> fn (x: &R int) -> x
- : (&R int -> &R int)
> region R { let x = &R 5 in 42 }
- : int = 42 // &R used inside, but result is int → OK
> region R { &R 5 }
ERROR: region escape: `&R int` cannot leave region `R`
> region R { region S { 100 } } // nesting OK
- : int = 100
> view Node[R] of int { value: int, next: int };
region R { let n = Node { value = 1, next = 0 } in n.value }
- : int = 1 // view construction is only inside a region
> view Slot[R] { item: &R int };
region S { let s = Slot { item = &S 42 } in 100 }
- : int = 100 // R is substituted to S
> view Node[R] of int { value: int };
let n = Node { value = 1 } in n.value // outside any region
ERROR: view Node must be constructed inside a region block
- Child regions (
region S of R { ... }) — promotion across nested regions. - Integration of
with+ Drop — lifecycle of Drop-bearing caps.
mere is a tree-walking interpreter written in OCaml, and in interpreter mode the actual memory management is done by OCaml's GC.
In Phase 4 codegen (C output), region becomes a real bump allocator (achieved 2026-06-18, Phase 4.17). region R { body } initializes the C runtime's __lang_region; &R v (sugar for R.alloc(v)) bump-allocates inside the region and returns T*; leaving the scope releases it all at once. Combined with the typer's escape check, leaving a region scope frees memory while the type signature guarantees no &R T value has leaked. Details in codegen.md's Phase 4.17.
A region is "a box that shares the same lifetime"; we also need a way to safely express structures that point at each other inside (graphs, linked lists, ASTs, JSON trees, etc.). That's what view types are for.
// In Rust this is nearly unwritable: two mutually-referencing nodes
let a = Node { value = 1, next = ??? } // want ??? to be b
let b = Node { value = 2, next = a } // but a should also point to b
In ownership-based languages, cyclic references are fundamentally hard (Rc<RefCell<T>>, unsafe, custom arenas, etc.). Inside a region, everyone shares the same lifetime, so cycles are fine. View types capture this "in-region relational structure" as a type.
| Axiom | Meaning |
|---|---|
| immutable | View values cannot be modified after construction; cannot be reassigned. |
| region-scoped | Always tied to some region R; cannot leave R (the type bakes in [R]). |
| structural identity by region | Same-typed views inside the same region are identified — this is the basis on which cyclic references work safely. |
type Point = { x: int, y: int }; // ordinary record: lifetime via GC; region-independent
view Node[R] of int { value: int }; // view: bundle type tied to region R
- A record is just data. A view is a structure with the region tag baked into the type (the
[R]inNode[R]). - Field types can include
&R T:view Node[R] of int { next: &R Node[R] }for self-reference. - View values can only be accessed through
&R Node(structural identity is per region).
The physical layout (the inner type of T) and what the programmer manipulates (the Node type) are viewed as different things. Enables expressions like "internally a sequential int, but viewed as a Node struct" (a planned future feature).
View construction is restricted to inside a region block; the region parameter R at the declaration site is substituted with the innermost active region's name at construction time; and the view value's type itself carries the region tag as Name[R]. Field accesses / record updates propagate the region, and the view value is subject to escape checking.
view Node[R] of int { value: int, next: int };
region R { let n = Node { value = 1, next = 0 } in n.value } // 1
view Slot[R] { item: &R int };
region S {
let s = Slot { item = &S 7 } in
s.item // : &S int (R → S propagation)
} // ERROR: &S int would escape
region S {
let s = Slot { item = &S 7 } in
let take_s = fn (x: &S int) -> 99 in
take_s s.item // 99 (s.item is &S int)
}
region S { Cell { v = 1 } } // ERROR: Cell[S] cannot leave region S
let n = Node { ... } // ERROR: must be inside a region block
- Cyclic construction within the same region (a two-phase model: mutable construction phase + immutable use phase).
- Making views reachable only through
&R V(currently the view value appears in types directly). - Q-009's "structural identity by region" axiom (a strict semantics that identifies same-typed views).
The detailed design lives in the internal design notes (Q-009 resolved).
-
&R vvalue expressions (Phase 2.1, 2026-06-16). - Region escape check (
&R Tcannot leak outside R, Phase 2.1). -
view V[R] of T { ... }declarations (Phase 2.2, 2026-06-16, Q-009 paper-validated). - Region enforcement on view (construction only inside a region + R is substituted to the active region at construction, Phase 2.3, 2026-06-16).
- Type-level region tag on view values + field-access / record-update region propagation + escape check on views (Phase 2.4, 2026-06-17).
-
R.alloc(v)syntactic sugar (equivalent to&R v; parser inspects region_stack and desugars; Phase 2.5, 2026-06-17). -
Trivial[R]type constraint (declare Drop types withdrop type Name = ...; type error when a region holds a value containing such a type; Phase 2.6, 2026-06-17).
- Refined borrow annotations:
&shared write/&exclusive write(Q-004 resolved in Phase 11.1-11.3; the borrow checker covers all 4 mode × 4 mode = 10 conflict pairs by Phase 17.1/17.2). - Child regions and promotion (
region S of R,R.promote(...)). - Region std-types like
Vec[R, T]/StrBuf[R](Q-010 narrowed). - Mechanizing
with+ Drop ordering (per Q-011 resolved).
The syntax and borrow checker for all 4 borrow modes are complete (Phase 11-17). However, with no concurrent backend yet, type-level requirements that T be "internally safe" (Rust's Send/Sync) for &shared write R T are not yet enforced:
| Mode | Concurrency-safety requirement (future) |
|---|---|
&R T (default = shared read) |
Safe — read-only access on immutable T |
&shared write R T |
Requires: T is internally safe (atomic / Mutex etc.) — not yet enforced |
&exclusive R T (= exclusive read) |
Safe — single-thread access only |
&mut R T (= exclusive write) |
Safe — single-thread access only |
Concurrent-backend prerequisites are collected in DEFERRED §2.4. Currently Mere is single-threaded interpreter + 3 single-threaded backends (C / LLVM / Wasm); &shared write is only a syntactic distinction and runtime-wise behaves like a plain &R T pointer. Introducing a concurrent backend (e.g. via OCaml domains / Wasm threads) is the trigger for adding Send/Sync-equivalent type bounds for T.
In progress — see codegen.md.
- C codegen MVP (int + arithmetic + if + let, 2026-06-17).
- Function lifting + recursion (factorial / fibonacci works, 2026-06-17).
- Strings + print + concat (hello world, 2026-06-18).
- Functions taking/returning str (2026-06-18).
- Tuples + per-AST type annotations (2026-06-18).
- Records / variants / pattern match (2026-06-18; includes polymorphic monomorphization).
- Closure conversion + first-class functions (2026-06-18).
- Region runtime (bump allocator) — the memory model in action, 2026-06-18.
-
withDrop execution codegen (2026-06-18; auto-invokescloseat scope end). - View construction over region (2026-06-18; view values become bump-alloc + pointer).
- Default-region closure env (2026-06-18, Phase 4.20; program-lifetime arena
__lang_default_regionis init/freed inmain; closure env alloc moves to bump). - Default-region for strings / recursive variant nodes (2026-06-18, Phase 4.21;
__lang_str_concatand recursive Constr's malloc go through the default region — user-visible malloc disappears entirely). - Move to LLVM IR or Wasm.
Specific design decisions live in the internal design notes:
| Doc | Content | Status |
|---|---|---|
00_design_principles.md |
Mere's philosophy and assumptions | — |
01_memory_model.md |
Overview of the 5 strategies | — |
02_json_parser_example.md |
Region's canonical use case | — |
03_lifetime_and_mutability.md |
Lifetime subtyping | — |
04_fundamental_tradeoffs.md |
Staged annotations, etc. | — |
08_effect_granularity.md |
Q-004 borrow refinement | narrowed |
11_region_vs_arena.md |
Q-008 unification | resolved |
12_drop_and_with.md |
Q-011 Drop ordering | resolved |
13_region_std_types.md |
Q-010 region std types | narrowed |
14_view_types.md |
Q-009 view-type axioms | resolved |
| Reference | Content |
|---|---|
| Tofte & Talpin (1997) | "Region-Based Memory Management" — the foundational region calculus |
| Cyclone (2002) | A research language that extends C with lifetimes and regions |
| Cone (2018-) | A modern language with region as a primitive |
| Vale (2020-) | Region + generational references |
| Mike Acton et al. | "Data-Oriented Design" — practical examples of frame-arena patterns |
Bottom line: Mere is designed to map memory lifetime onto program structure — looser than ownership but stricter than GC; permits cycles; predictable. Currently mere is at Phase 1 (syntax-only); the real power emerges in Phase 2+'s static checks + codegen.