Mini Spec — Borrow/Mutate Views on a GC Heap (Prometeu) This document defines an optional “Rust-flavored” access discipline for heap objects without using RC/HIP. It is compatible with a GC-based heap, first-class functions (closures), and deterministic coroutines. Goals Preserve the feel of borrow (shared read) and mutate (exclusive write). Prevent common aliasing bugs in user code. Keep the runtime simple and GC-friendly. Avoid restrictions that would block closures/coroutines. Non-goals Not a full Rust borrow checker. Not a memory management mechanism (GC owns lifetimes). Not a general concurrency/locking system. 1. Concepts 1.1 Heap handle A heap object is referenced by a handle. Handles can be stored, copied, captured, and returned freely (GC-managed). 1.2 Views A view is a temporary capability derived from a handle: BorrowView: shared read-only view MutView: exclusive mutable view Views are stack-only and lexically scoped. 2. Core Rules (Language Semantics) 2.1 Creating views Views can be created only by dedicated constructs: borrow(h) { ... } produces a BorrowView for the duration of the block mutate(h) { ... } produces a MutView for the duration of the block 2.2 View locality Views must never escape their lexical scope: A view cannot be: stored into heap objects captured by closures returned from functions yielded across coroutine suspension points stored into globals (Think: “views are ephemeral; handles are persistent.”) 2.3 Access permissions Within a view scope: BorrowView permits reads only MutView permits reads and writes Outside a view scope: direct field access is not permitted; you must open a view scope again. 2.4 Exclusivity At any program point, for a given handle h: Multiple BorrowView(h) may coexist. Exactly one MutView(h) may exist, and it excludes all borrows. This is enforced at compile time (required) and optionally at runtime in debug mode (see §5). 2.5 Re-entrancy Within a mutate(h) scope: nested borrow(h) is allowed (it is trivially compatible) nested mutate(h) is disallowed Within a borrow(h) scope: nested mutate(h) is disallowed 3. Interaction with Closures and Coroutines 3.1 Closures Closures may capture handles freely. Closures must not capture views. If code inside borrow/mutate creates a closure, it may capture the handle, but not the view. 3.2 Coroutines Coroutines may store handles in their stacks/locals. Views must not live across suspension: A coroutine cannot yield while a view is active. A coroutine cannot sleep while a view is active. This rule guarantees that view lifetimes remain local and deterministic. 4. Compiler Requirements (Static Enforcement) The compiler enforces this feature by tracking view lifetimes as lexical regions. Minimum checks: A view cannot be assigned to a variable with heap lifetime. A view cannot appear in a closure capture set. A view cannot be returned. A view cannot be live at FRAME_SYNC, YIELD, SLEEP, or any safe-point boundary defined by the language. Exclusivity rules per handle region (borrow vs mutate overlaps) must hold. If a rule is violated: compile error (fatal). 5. Optional Runtime Checking (Debug Mode) Runtime checking is optional and intended for debugging or untrusted bytecode scenarios. 5.1 Object header borrow-state Each heap object may maintain: readers: u16 writer: bool Runtime actions: entering borrow increments readers if no writer leaving borrow decrements readers entering mutate requires readers==0 and writer==false, then sets writer=true leaving mutate sets writer=false Violations trap with a specific diagnostic. 5.2 Build profiles Release builds may omit this entirely (zero overhead). Debug builds may enable it. 6. Lowering to Bytecode (Suggested) This feature does not require dedicated opcodes in the minimal design. Recommended lowering strategy: borrow/mutate are compile-time constructs. Field reads/writes lower to existing heap access opcodes (FIELD_GET/FIELD_SET or equivalent), but only permitted while a view is considered “active” by the compiler. In debug-runtime-check mode, the compiler may insert explicit enter/exit markers (or syscalls) to enable runtime enforcement. 7. Rationale GC removes the need for RC/HIP, but aliasing mistakes still exist. Views provide a clean and didactic discipline: Handles: long-lived, capturable, GC-managed. Views: short-lived, local, safe access windows. This keeps the VM simple while enabling expressive language features (closures, coroutines) without lifetime headaches. 8. Summary Borrow/Mutate are access disciplines, not memory management. Views are stack-only and non-escapable. Closures and coroutines freely use handles, but never views. Enforcement is compile-time, with optional runtime checks in debug mode.