Contents

Memory management model (mere)

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.


1. Comparison of memory-management strategies

StrategyWhen is memory freed?ExamplesStrengthsWeaknesses
Manual (malloc/free)The programmer calls free each timeCMaximum controlFrequent use-after-free / leaks
GCA runtime garbage collector decidesOCaml, Java, Go, PythonSafe, easyPause times, memory overhead, poor for real-time
Ownership (move + borrow)Automatically at scope exitRustCompile-time guarantees, zero costCyclic / self-references are painful; learning curve
Region (bulk per-region)The whole region scope is freed at onceCone, Vale, Cyclone, ML/Talpin researchFast alloc / bulk free; cyclic refs OKRequires designing region lifetimes
Stack-onlyAt stack-frame exitC autos, Rust letZero costStrict size/lifetime constraints

Mere is designed to let you mix and match: the programmer chooses a strategy explicitly, and the compiler verifies safety.


2. Mere's memory strategies (5)

Based on the design note 01_memory_model.md:

owned T — sole ownership

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

&borrowed T — borrow

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).

region R { ... } — bulk per-region

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.

view V[R] of T — self-referential views (Q-009)

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
}

stack { ... } — stack-only

Guarantees the value lives only on the stack. No heap allocation.


3. Why region (in detail)

Problems it solves

Areas where Rust's ownership struggles:

Region resolves these via bulk-free per region.

How it works


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

Typical uses

The Trivial constraint

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)

4. Current state in mere (as of 2026-06-24, Phase 46)

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:

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:

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 { 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

What doesn't work yet

Why "syntax only"

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](codegen.md)'s Phase 4.17.


5. View types — self-referential / cyclic structures inside a region

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.

Motivation: weak spots of ownership


// 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>, 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.

Three axioms (Q-009 paper-validated)

AxiomMeaning
immutableView values cannot be modified after construction; cannot be reassigned.
region-scopedAlways tied to some region R; cannot leave R (the type bakes in [R]).
structural identity by regionSame-typed views inside the same region are identified — this is the basis on which cyclic references work safely.

Difference from records


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

Why "view"?

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).

Current state (Phase 2.4, 2026-06-17)

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

Tightening planned for later phases

The detailed design lives in the internal design notes (Q-009 resolved).


6. Roadmap

Phase 2 (medium-sized, ~600-800 LoC, multiple slices) — in progress

Phase 3 (larger; some design needs to be revisited)

Current state of borrow modes and concurrency safety (2026-06-23 update)

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:

ModeConcurrency-safety requirement (future)
&R T (default = shared read)Safe — read-only access on immutable T
&shared write R TRequires: 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.

Phase 4 (codegen)

In progress — see codegen.md.


7. Design context (in detail)

Specific design decisions live in the internal design notes:

DocContentStatus
00_design_principles.mdMere's philosophy and assumptions
01_memory_model.mdOverview of the 5 strategies
02_json_parser_example.mdRegion's canonical use case
03_lifetime_and_mutability.mdLifetime subtyping
04_fundamental_tradeoffs.mdStaged annotations, etc.
08_effect_granularity.mdQ-004 borrow refinementnarrowed
11_region_vs_arena.mdQ-008 unificationresolved
12_drop_and_with.mdQ-011 Drop orderingresolved
13_region_std_types.mdQ-010 region std typesnarrowed
14_view_types.mdQ-009 view-type axiomsresolved

8. Academic roots

ReferenceContent
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.