diff --git a/CLAUDE.md b/CLAUDE.md index d8b4b72..c093268 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -160,7 +160,7 @@ directory argument: `grep` prints `bench/: Is a directory` and silently skips it otherwise. There used to be a guard here matching `&X = bareSymbol`. It is **gone** and -must not be restored: a bare symbol is a guest source (`spec/memory.md` §2.8), +must not be restored: a bare symbol is a reference source (`spec/memory.md` §2.8), so a match indicates nothing either way — what governs such an assignment is the scope comparison in `spec/lifetimes.md` §1.1, which needs the declaration scopes of both sides and so cannot be grepped at all. @@ -180,6 +180,22 @@ reintroduction; fix it. Merged stories say "receiver" throughout and stay that way, so the two trees disagree on this word by design. Use `subject` in new prose on both sides. +**`guest`, `host`, and the lifetime `owner`.** The `&` is a **reference**, the +slot that holds an object is its **owner**, and what the store rule compares is +a place's **scope** (glossary §3.32, §3.33, §3.43). Each guard comes back empty: + +```sh +grep -RIn -i -E "guest|\bhost" spec/ | grep -vF '> **Story:**' | grep -v "^spec/dependencies.md" +grep -RIn -E "(block|scope)s? owns?\b|owned by (the |its |that |this |an? )?(declaring |enclosing |body )?(block|scope)|owner comparison" spec/ +grep -RIn -i -E "\breference (field|element|parameter|member|slot)s?\b" spec/ +``` + +The first skips `> **Story:**` pointers, whose quoted chapter headings keep the +old words, and `dependencies.md`, where a host is a download server. The second +catches the old lifetime `owner` coming back: a block is a scope, never an +owner. The third guards spec guide §6.6. Merged stories keep the old words, +as with `receiver`. + **The separator.** The bracket picks the separator (canonical home `spec/lexical.md` §6). `init{ }` and the field-constructor header and call site took `,` under the previous rule, and every other C-family language still does, diff --git a/README.md b/README.md index a71d63d..3c683d2 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ The specification lives in [`spec/`](spec/) and is organized by topic. Each docu | [`spec/adt.md`](spec/adt.md) | Enums, variants, the struct/variant symmetry, pattern matching, `match`, and enum maps | | [`spec/functions.md`](spec/functions.md) | Methods, free functions, subscripts, overload resolution, function values, and lambdas | | [`spec/generics.md`](spec/generics.md) | Unified type parameters, `<>` type expressions, constructor calls, and the `Array`, `ArrayRef`, and `List` container primitives | -| [`spec/memory.md`](spec/memory.md) | Hosting, settled and roaming hosts, guests, and arena layout | +| [`spec/memory.md`](spec/memory.md) | Ownership, settled and roaming owners, references, and arena layout | | [`spec/lifetimes.md`](spec/lifetimes.md) | Lexical lifetime rules, moves, scope rules, and deterministic destruction | | [`spec/effects.md`](spec/effects.md) | The effect model, `mut`, read-only bindings, what each kind of verb may write, and capability wiring | | [`spec/concurrency.md`](spec/concurrency.md) | Implicit parallelism, `spawn`, water-tower lifetimes, and concurrency safety | @@ -69,7 +69,7 @@ The spec states *what* the language does; the **why** lives in a parallel set of | [`stories/adt.md`](stories/adt.md) | [`spec/adt.md`](spec/adt.md) — splitting `enum` from `variant` against the hype, the shared struct body, escaping the matcher machine with case overloads and the turn to a central `match` block, matching variants rather than patterns, keeping enum data outside the members, reducing a match group to sugar for one arm per case, building a variant by naming a case rather than calling a constructor, the bindings that existed only to be pointed at, and making a recursive member an owned child boxed through a hosting handle rather than an `&`, and the sum that could not contain itself until a value copy became deep, and why a variant payload cannot originate a guest even though the whole reference variant can, with displaced hosting payloads floating within the same owner | | [`stories/generics.md`](stories/generics.md) | [`spec/generics.md`](spec/generics.md) — the parameter model, the `<>`/`()` split, size-in-the-type, the deferred features, why a language without pointers needed a second, dynamically sized container primitive, and why an integer literal and a decimal literal carry different concept types, with the number parameter taking the integer one, and why a wrong-kind type argument is reported where the type was supplied | | [`stories/dependencies.md`](stories/dependencies.md) | [`spec/dependencies.md`](spec/dependencies.md) — URL identity, the manifest/resolution split, prebuilt distribution, symbol-rewriting, the browsable global cache, the package-graph acyclicity rule, opt-in remapping, why `core` became a bundled implementation package, the floor that finally made it an ordinary one, moving prebuilt objects to Release assets with committed hashes, and recording where each dependency's code comes from — a release, a source build, or a local path — beside a lock file renamed `zane-lock.coda`, the manifest field that says whether a project is a library or an application, and the `zane` command that launches the pinned compiler without being pinned itself | -| [`stories/memory.md`](stories/memory.md) | [`spec/memory.md`](spec/memory.md) — the no-GC-no-lifetimes goal, the move problem and the anchor, lazy backpointer creation, the indexed heap table, the rooted-guest rules and the host/guest terminology split, the collapse to one value/reference axis with a borrowed subject, the shift to segmented chunked bump arenas, the split into fixed-size and dynamic regions with anchors moved to a runtime-global recyclable pool, taking the bare symbol away as a guest source, the three passing modes that split out of it, giving both back when the ban cost more than the question it closed, and the dynamic region taking the boxes, each asking for exactly its own size, once recursion became hosting, and what a copy is for — the deep value copy that lets a value type recurse, and the reference-field ban that survived it, and the stable-place boundary that excludes subscripted paths and variant payloads from guest sources, and same-owner floating when contingent hosts disappear, what a move does to guests of the moved struct's fields, strings keeping their dynamic bytes without reference identity, the settled/roaming split that stopped guestable hosts from moving, the borrow returning as the bare spelling of a reference parameter, guests becoming plain offsets once anchors, backpointers and forwarders had nothing left to do, settled overwrites staying in place with only an escape relocating, and `ArrayRef` giving many settled objects a home | +| [`stories/memory.md`](stories/memory.md) | [`spec/memory.md`](spec/memory.md) — the no-GC-no-lifetimes goal, the move problem and the anchor, lazy backpointer creation, the indexed heap table, the rooted-guest rules and the host/guest terminology split, the collapse to one value/reference axis with a borrowed subject, the shift to segmented chunked bump arenas, the split into fixed-size and dynamic regions with anchors moved to a runtime-global recyclable pool, taking the bare symbol away as a guest source, the three passing modes that split out of it, giving both back when the ban cost more than the question it closed, and the dynamic region taking the boxes, each asking for exactly its own size, once recursion became hosting, and what a copy is for — the deep value copy that lets a value type recurse, and the reference-field ban that survived it, and the stable-place boundary that excludes subscripted paths and variant payloads from guest sources, and same-owner floating when contingent hosts disappear, what a move does to guests of the moved struct's fields, strings keeping their dynamic bytes without reference identity, the settled/roaming split that stopped guestable hosts from moving, the borrow returning as the bare spelling of a reference parameter, guests becoming plain offsets once anchors, backpointers and forwarders had nothing left to do, settled overwrites staying in place with only an escape relocating, and `ArrayRef` giving many settled objects a home, and dropping the verbless `guest` for `reference` and `host` for `owner`, with the store rule's lifetime becoming the `scope` | | [`stories/lifetimes.md`](stories/lifetimes.md) | [`spec/lifetimes.md`](spec/lifetimes.md) — lexical scope in place of a borrow checker, what may be moved, the declaration-block rule that kills flow analysis, downgrade instead of use-after-move, parameter-rooted returned guests, why each strict rule is the minimal guard against one specific memory corruption, narrowing a returned guest's root to a guest parameter once borrows arrived, the root rule going back to "any parameter" once they left, the scope check that fired once at wiring and could be outrun by a later move, and the two lifetimes hiding behind that fix — a block and a hosting tree — which collapsed the raise enumeration into one owner comparison made at every store, paid for with a signature that records where a parameter comes to rest — and the stricter `init`-as-empty-template design that would have needed no signatures at all, kept on record with the two costs that turned it down, and the later distinction between an owner's lifetime and an interior place stable enough to originate a guest, plus same-owner floating when that contingent place disappears, and a moved host becoming spent rather than a guest, the three contradictions found by running the examples as compiler tests, and the parameter that lived at the call site going away once nothing guested could move | | [`stories/effects.md`](stories/effects.md) | [`spec/effects.md`](spec/effects.md) — inferring effects instead of annotating them, subject-scoped `mut`, capabilities in place of ambient I/O, the four-level ladder and the Total-Pure/Pure split, what deliberately is not an effect, and mutation through a borrowed subject, and where the first capability comes from: the runtime and console as objects only the root package reaches, and a mutating call as a write, which retired the four-level ladder | | [`stories/concurrency.md`](stories/concurrency.md) | [`spec/concurrency.md`](spec/concurrency.md) — the parallelism/concurrency split and the refusal of `async` coloring, why `spawn` marks only a call, water-tower lifetimes, signature-based safety without locks, value-typed mutation closing the aliased-write gap, and the two safety rules that turned out to enforce less than they claimed | diff --git a/bench/benchmark.html b/bench/benchmark.html index 4af3b10..fbe7699 100644 --- a/bench/benchmark.html +++ b/bench/benchmark.html @@ -121,7 +121,7 @@

Zane Memory Model Benchmark

"meta": [ { "label": "Object size", - "val": "32B \u2014 no per-host metadata" + "val": "32B \u2014 no per-object metadata" }, { "label": "Alloc cost", @@ -140,8 +140,8 @@

Zane Memory Model Benchmark

"val": "20 \u2014 median reported" } ], - "setup": "One fixed-region frontier bump per host, with a chunk-boundary check. A host carries no header, so allocation writes nothing into the object. Release is a no-op \u2014 the fixed-size region reclaims only when the scope drains. The arena row is a flat bump with no chunk boundary.", - "note": "Zane's bump (~86us) runs ~58x faster than malloc (~5.03ms) and ~6.5x faster than Pool (~559us), and since only Zane's release is free, its figure is allocation alone where the other two carry their frees inside it. Neither Zane nor the flat arena (~39us) writes to the memory it hands out now that a host carries no backpointer, so the ~2.2x between them is not cache-line fills: the only code that differs is Zane's chunk-boundary check and the frontier it keeps in its region record. The first of Zane's twenty passes took ~1.14ms, 13x its median; the harness touches every page of the region before any test runs, so that pass is warm-up of something other than page faults, and the median is the figure to read.", + "setup": "One fixed-region frontier bump per object, with a chunk-boundary check. An object carries no header, so allocation writes nothing into the object. Release is a no-op \u2014 the fixed-size region reclaims only when the scope drains. The arena row is a flat bump with no chunk boundary.", + "note": "Zane's bump (~86us) runs ~58x faster than malloc (~5.03ms) and ~6.5x faster than Pool (~559us), and since only Zane's release is free, its figure is allocation alone where the other two carry their frees inside it. Neither Zane nor the flat arena (~39us) writes to the memory it hands out now that an object carries no backpointer, so the ~2.2x between them is not cache-line fills: the only code that differs is Zane's chunk-boundary check and the frontier it keeps in its region record. The first of Zane's twenty passes took ~1.14ms, 13x its median; the harness touches every page of the region before any test runs, so that pass is warm-up of something other than page faults, and the median is the figure to read.", "noteStale": false }, { @@ -185,7 +185,7 @@

Zane Memory Model Benchmark

} ], "setup": "Alloc and shuffle untimed. The fixed-size region has no free list and no coalescing, so release is a no-op regardless of order.", - "note": "Zane's row is not a fast release loop; it is no loop at all. zm_host_release is an empty function, so the compiler deletes the 100k-iteration loop outright, and the harness reports the row as eliminated rather than timing two timestamps around nothing. malloc (~2.27ms) and Pool (~681us) spend ~23ns and ~6.8ns an object returning blocks to a structure that later allocations must search. Having the structure at all is the cost.", + "note": "Zane's row is not a fast release loop; it is no loop at all. zm_own_release is an empty function, so the compiler deletes the 100k-iteration loop outright, and the harness reports the row as eliminated rather than timing two timestamps around nothing. malloc (~2.27ms) and Pool (~681us) spend ~23ns and ~6.8ns an object returning blocks to a structure that later allocations must search. Having the structure at all is the cost.", "noteStale": false }, { @@ -382,8 +382,8 @@

Zane Memory Model Benchmark

"noteStale": false }, { - "t": "T6 \u2014 guest access", - "title": "Guest access via a segmented offset vs a direct pointer", + "t": "T6 \u2014 reference access", + "title": "Reference access via a segmented offset vs a direct pointer", "labels": [ "Segmented offset (chunk dir reloaded)", "Direct pointer (baseline)", @@ -417,31 +417,31 @@

Zane Memory Model Benchmark

}, { "label": "Segmented offset, dir cached", - "val": "chunk directory hoisted; offset \u2192 host" + "val": "chunk directory hoisted; offset \u2192 object" }, { "label": "Segmented offset, dir reloaded", "val": "chunk directory re-fetched per access" }, { - "label": "Guest size", + "label": "Reference size", "val": "u32 segmented offset \u2014 half a 64-bit pointer" }, { - "label": "Guest cost", - "val": "4B \u2014 the guest itself; the host stores nothing" + "label": "Reference cost", + "val": "4B \u2014 the reference itself; the owner stores nothing" }, { "label": "Asserted", - "val": "every guest resolves to the host it was minted from" + "val": "every reference resolves to the object it was minted from" }, { "label": "Runs", "val": "20 \u2014 median reported" } ], - "setup": "A guest is the u32 segmented offset of the settled host it names (memory.md \u00a74.1). Resolving it is a shift, a mask and one chunk-directory load, then the host itself. Nothing is allocated to mint a guest and nothing is recorded in the host.", - "note": "A guest now costs what a pointer costs. Resolving a segmented offset (~195us with the chunk directory hoisted, ~168us re-fetched) ties dereferencing a raw pointer (~191us); the fastest passes, ~157us direct against ~163-166us offset, agree to within 6%, and the re-fetched row coming in ahead is noise. Under anchors the same test cost ~1.24x direct, which was the anchor-cell load a guest no longer makes. The shift and mask that turn an offset into an address are absorbed by the pipeline.", + "setup": "A reference is the u32 segmented offset of the settled owner it names (memory.md \u00a74.1). Resolving it is a shift, a mask and one chunk-directory load, then the object itself. Nothing is allocated to mint a reference and nothing is recorded in the owner.", + "note": "A reference now costs what a pointer costs. Resolving a segmented offset (~195us with the chunk directory hoisted, ~168us re-fetched) ties dereferencing a raw pointer (~191us); the fastest passes, ~157us direct against ~163-166us offset, agree to within 6%, and the re-fetched row coming in ahead is noise. Under anchors the same test cost ~1.24x direct, which was the anchor-cell load a reference no longer makes. The shift and mask that turn an offset into an address are absorbed by the pipeline.", "noteStale": false }, { @@ -495,7 +495,7 @@

Zane Memory Model Benchmark

"val": "20 \u2014 median reported" } ], - "setup": "Each spawn is a fixed-region bump; each kill is a no-op release. These are statically sized hosts in the fixed-size region, which reclaims in bulk at drain.", + "setup": "Each spawn is a fixed-region bump; each kill is a no-op release. These are statically sized objects in the fixed-size region, which reclaims in bulk at drain.", "note": "Once entity counts stabilize the allocator stops mattering much: Zane (~4.28ms), Pool (~4.56ms) and malloc (~4.99ms) span ~17%, with Zane nominally in front of Pool by ~7%. The previous pin had Zane and Pool tied, so read that lead as inside the noise. The order-of-magnitude gaps of T1-T3 have gone into per-frame update work.", "noteStale": false }, @@ -615,7 +615,7 @@

Zane Memory Model Benchmark

} ], "setup": "Phases A and B untimed, phase C timed. Phase B's releases are no-ops, so the refill simply bumps the frontier past the dead space.", - "note": "A bump frontier cannot be fragmented, and the refill shows it: Zane takes ~49us for the 50k objects, ~1ns each, and holds between ~48us and ~54us across all 20 passes. It is also the one row in the suite that got faster than the previous pin in absolute terms, ~77us then, on a run where every unchanged row got slower: refilling no longer zeroes a backpointer in each host. malloc (~2.28ms) and Pool (~1.19ms) refill from freed slots at ~46x and ~24x, and both degrade across the run: Pool's passes climb from ~379us to a ~2.24ms worst and malloc's from ~1.49ms to ~3.01ms, each cycle handing the free list back in a worse order than it found it.", + "note": "A bump frontier cannot be fragmented, and the refill shows it: Zane takes ~49us for the 50k objects, ~1ns each, and holds between ~48us and ~54us across all 20 passes. It is also the one row in the suite that got faster than the previous pin in absolute terms, ~77us then, on a run where every unchanged row got slower: refilling no longer zeroes a backpointer in each object. malloc (~2.28ms) and Pool (~1.19ms) refill from freed slots at ~46x and ~24x, and both degrade across the run: Pool's passes climb from ~379us to a ~2.24ms worst and malloc's from ~1.49ms to ~3.01ms, each cycle handing the free list back in a worse order than it found it.", "noteStale": false }, { @@ -669,13 +669,13 @@

Zane Memory Model Benchmark

"val": "20 \u2014 median reported" } ], - "setup": "A tree torn down by post-order DFS. Node payloads release as no-ops; each 128-byte child list goes back on its exact-size stack. A guest leaves nothing in its host, so how many guests a tree has cannot change its teardown.", - "note": "Teardown of the branching tree: Zane ~62us, Pool ~79us (~1.3x), malloc ~152us (~2.5x). This is the first pin on the corrected topology, roughly 1,070 branch points at depth ~20-26 rather than the 4,000-node chain the earlier figures measured, and the cross-allocator ordering survived the change. Guest density no longer has a row, because a guest leaves nothing in its host for teardown to visit; what Zane's walk does per node is return the node's child list to its exact-size stack.", + "setup": "A tree torn down by post-order DFS. Node payloads release as no-ops; each 128-byte child list goes back on its exact-size stack. A reference leaves nothing in its owner, so how many references a tree has cannot change its teardown.", + "note": "Teardown of the branching tree: Zane ~62us, Pool ~79us (~1.3x), malloc ~152us (~2.5x). This is the first pin on the corrected topology, roughly 1,070 branch points at depth ~20-26 rather than the 4,000-node chain the earlier figures measured, and the cross-allocator ordering survived the change. Reference density no longer has a row, because a reference leaves nothing in its owner for teardown to visit; what Zane's walk does per node is return the node's child list to its exact-size stack.", "noteStale": false }, { "t": "T11 \u2014 stress test", - "title": "Fragmentation stress: hosts + lists, random spawn / push / kill cycles", + "title": "Fragmentation stress: objects + lists, random spawn / push / kill cycles", "labels": [ "Zane (fixed bump + size stacks)", "malloc / free", @@ -728,7 +728,7 @@

Zane Memory Model Benchmark

"val": "20 \u2014 median reported" } ], - "setup": "Entities are fixed-region hosts whose release is a no-op; list backing stores are dynamic blocks that start at 128B, double, and return to their exact-size stacks. Both regions run at once here.", + "setup": "Entities are fixed-region objects whose release is a no-op; list backing stores are dynamic blocks that start at 128B, double, and return to their exact-size stacks. Both regions run at once here.", "note": "The mixed workload compresses the field to a tie: Zane ~3.89ms, malloc ~4.08ms, Pool ~4.12ms, ~6% end to end. Updates, scans and randomized maintenance dominate, so the allocator is a small slice of the total, and the ordering has moved between every pin.", "noteStale": false }, @@ -736,8 +736,8 @@

Zane Memory Model Benchmark

"t": "T12 \u2014 concurrent scan", "title": "Concurrent shard scan over four independent Array<Entity, 25000> workloads", "labels": [ - "Hosted Array shards, sequential", - "Hosted Array shards, concurrent (4 workers)" + "Owned Array shards, sequential", + "Owned Array shards, concurrent (4 workers)" ], "data": [ 113.7, @@ -782,7 +782,7 @@

Zane Memory Model Benchmark

"val": "20 \u2014 median reported" } ], - "setup": "Four read-only shards of one hosted inline array, summed either sequentially or on four worker threads. Each run asserts the aggregate matches the deterministic baseline.", + "setup": "Four read-only shards of one owned inline array, summed either sequentially or on four worker threads. Each run asserts the aggregate matches the deterministic baseline.", "note": "Concurrency loses. The scan splits into four equal shards and waits for all of them, so a pass runs at the speed of its slowest shard: concurrent passes run ~248us at best, ~398us median and ~647us at worst, against ~114us sequential (~89us best). Best against best is ~2.8x, close to the ~2.9x of the previous pin and the ~3.1x an earlier run measured with the process pinned to four cores, which is the figure to read; the median adds core placement on top. Distributing a ~100us scan costs several times more than doing it outright, because per-shard work this small cannot amortize the handoff.", "noteStale": false }, @@ -865,7 +865,7 @@

Zane Memory Model Benchmark

"labels": [ "Construct fresh value tree in place", "Deep-copy value tree (place source)", - "Escape roaming hosted tree (relocate)", + "Escape roaming owned tree (relocate)", "malloc deep copy" ], "data": [ @@ -899,7 +899,7 @@

Zane Memory Model Benchmark

"val": "complete binary, depth 12 \u2014 8,191 nodes" }, { - "label": "Hosted node", + "label": "Owned node", "val": "16B \u2014 value and two handles" }, { @@ -928,14 +928,14 @@

Zane Memory Model Benchmark

}, { "label": "Asserted", - "val": "overwriting a settled boxed member, and the boxed member inside it, reuses both blocks; guests to either keep their address and see the replacement" + "val": "overwriting a settled boxed member, and the boxed member inside it, reuses both blocks; references to either keep their address and see the replacement" }, { "label": "Runs", "val": "20 \u2014 median reported" } ], - "setup": "A recursive tree whose members are boxed (adt.md \u00a74): a fixed-size handle inline, the payload in the dynamic region at exactly the node size. A move within the scope that holds the blocks copies only the root's handles, so it is not timed. An escape out of that scope relocates every boxed descendant recursively and returns each old block (memory.md \u00a73.5); nothing inside a roaming host is guested, so nothing else is updated. A value copy reallocates every payload so the two share no storage (\u00a72.3); fresh construction builds each node in place and copies nothing.", + "setup": "A recursive tree whose members are boxed (adt.md \u00a74): a fixed-size handle inline, the payload in the dynamic region at exactly the node size. A move within the scope that holds the blocks copies only the root's handles, so it is not timed. An escape out of that scope relocates every boxed descendant recursively and returns each old block (memory.md \u00a73.5); nothing inside a roaming owner is referenced, so nothing else is updated. A value copy reallocates every payload so the two share no storage (\u00a72.3); fresh construction builds each node in place and copies nothing.", "note": "An escape costs what a deep copy costs. Relocating the roaming tree out of its scope (~84us) and deep-copying the value tree from an existing place (~81us) both walk all 8,191 nodes and allocate a block for each; the escape additionally returns each old block to its stack, and the two tie. A move that does not escape copies only the root's handles and is not timed. Constructing a fresh value tree in place (~46us) is the floor both sit ~1.8x above, which is the copy itself and why only an existing source or an escape pays it. malloc's deep copy is ~342us, ~4.2x.", "noteStale": false } diff --git a/bench/benchmeta.py b/bench/benchmeta.py index cc58279..7106fea 100644 --- a/bench/benchmeta.py +++ b/bench/benchmeta.py @@ -7,11 +7,11 @@ # ───────────────────────────────────────────────────────────── # Test metadata: short name, title, setup, and per-impl facts. -# These track spec/memory.md: hosting is the default and the guest (&) is -# opt-in; a scope owns a fixed-size region and a dynamic region that never -# share a chunk; a reference-type host is settled (guestable, never moves) or -# roaming (moves, guested by nothing); and a guest is the u32 segmented offset -# of the settled host it names. A host carries no metadata of its own. +# These track spec/memory.md: ownership is the default and the reference (&) +# is opt-in; a scope has a fixed-size region and a dynamic region that never +# share a chunk; an owner is settled (referenceable, never moves) or roaming +# (moves, referenced by nothing); and a reference is the u32 segmented offset +# of the settled owner it names. An owned object carries no metadata of its own. # # The "Reading the result" note for each test is NOT stored here. It is read # from explanations.txt — result interpretation authored after looking at a @@ -23,9 +23,9 @@ "Test 1": { "short": "T1 — seq alloc+free", "title": "Sequential alloc then sequential free", - "setup": "One fixed-region frontier bump per host, with a chunk-boundary check. A host carries no header, so allocation writes nothing into the object. Release is a no-op — the fixed-size region reclaims only when the scope drains. The arena row is a flat bump with no chunk boundary.", + "setup": "One fixed-region frontier bump per object, with a chunk-boundary check. An object carries no header, so allocation writes nothing into the object. Release is a no-op — the fixed-size region reclaims only when the scope drains. The arena row is a flat bump with no chunk boundary.", "meta": [ - ("Object size", "32B — no per-host metadata"), + ("Object size", "32B — no per-object metadata"), ("Alloc cost", "one fixed-region bump"), ("Release cost", "no-op — fixed region reclaims only at drain"), ("Arena row", "flat bump, no chunk boundary"), @@ -81,23 +81,23 @@ ], }, "Test 6": { - "short": "T6 — guest access", - "title": "Guest access via a segmented offset vs a direct pointer", - "setup": "A guest is the u32 segmented offset of the settled host it names (memory.md §4.1). Resolving it is a shift, a mask and one chunk-directory load, then the host itself. Nothing is allocated to mint a guest and nothing is recorded in the host.", + "short": "T6 — reference access", + "title": "Reference access via a segmented offset vs a direct pointer", + "setup": "A reference is the u32 segmented offset of the settled owner it names (memory.md §4.1). Resolving it is a shift, a mask and one chunk-directory load, then the object itself. Nothing is allocated to mint a reference and nothing is recorded in the owner.", "meta": [ ("Direct", "raw C pointer dereference — baseline"), - ("Segmented offset, dir cached", "chunk directory hoisted; offset → host"), + ("Segmented offset, dir cached", "chunk directory hoisted; offset → object"), ("Segmented offset, dir reloaded", "chunk directory re-fetched per access"), - ("Guest size", "u32 segmented offset — half a 64-bit pointer"), - ("Guest cost", "4B — the guest itself; the host stores nothing"), - ("Asserted", "every guest resolves to the host it was minted from"), + ("Reference size", "u32 segmented offset — half a 64-bit pointer"), + ("Reference cost", "4B — the reference itself; the owner stores nothing"), + ("Asserted", "every reference resolves to the object it was minted from"), ("Runs", "20 — median reported"), ], }, "Test 7": { "short": "T7 — game loop", "title": "Simulated game loop: spawn, kill, and update entities each frame", - "setup": "Each spawn is a fixed-region bump; each kill is a no-op release. These are statically sized hosts in the fixed-size region, which reclaims in bulk at drain.", + "setup": "Each spawn is a fixed-region bump; each kill is a no-op release. These are statically sized objects in the fixed-size region, which reclaims in bulk at drain.", "meta": [ ("Entity size", "32B"), ("Frame count", "500 frames"), @@ -134,7 +134,7 @@ "Test 10": { "short": "T10 — tree teardown", "title": "Cascade destruction — Zane vs malloc and pool", - "setup": "A tree torn down by post-order DFS. Node payloads release as no-ops; each 128-byte child list goes back on its exact-size stack. A guest leaves nothing in its host, so how many guests a tree has cannot change its teardown.", + "setup": "A tree torn down by post-order DFS. Node payloads release as no-ops; each 128-byte child list goes back on its exact-size stack. A reference leaves nothing in its owner, so how many references a tree has cannot change its teardown.", "meta": [ ("Tree size", "~4,000 nodes, branch 0–6"), ("Child lists", "128B dynamic blocks returned to the size stack"), @@ -145,8 +145,8 @@ }, "Test 11": { "short": "T11 — stress test", - "title": "Fragmentation stress: hosts + lists, random spawn / push / kill cycles", - "setup": "Entities are fixed-region hosts whose release is a no-op; list backing stores are dynamic blocks that start at 128B, double, and return to their exact-size stacks. Both regions run at once here.", + "title": "Fragmentation stress: objects + lists, random spawn / push / kill cycles", + "setup": "Entities are fixed-region objects whose release is a no-op; list backing stores are dynamic blocks that start at 128B, double, and return to their exact-size stacks. Both regions run at once here.", "meta": [ ("Object size", "32B"), ("List blocks", "128 / 256 / 512B, cache-line aligned"), @@ -159,7 +159,7 @@ "Test 12": { "short": "T12 — concurrent scan", "title": "Concurrent shard scan over four independent Array<Entity, 25000> workloads", - "setup": "Four read-only shards of one hosted inline array, summed either sequentially or on four worker threads. Each run asserts the aggregate matches the deterministic baseline.", + "setup": "Four read-only shards of one owned inline array, summed either sequentially or on four worker threads. Each run asserts the aggregate matches the deterministic baseline.", "meta": [ ("Workers", "4"), ("Shard size", "25,000 entities"), @@ -186,17 +186,17 @@ "Test 14": { "short": "T14 — boxed members", "title": "Boxed members: roaming escape vs deep value copy", - "setup": "A recursive tree whose members are boxed (adt.md §4): a fixed-size handle inline, the payload in the dynamic region at exactly the node size. A move within the scope that holds the blocks copies only the root's handles, so it is not timed. An escape out of that scope relocates every boxed descendant recursively and returns each old block (memory.md §3.5); nothing inside a roaming host is guested, so nothing else is updated. A value copy reallocates every payload so the two share no storage (§2.3); fresh construction builds each node in place and copies nothing.", + "setup": "A recursive tree whose members are boxed (adt.md §4): a fixed-size handle inline, the payload in the dynamic region at exactly the node size. A move within the scope that holds the blocks copies only the root's handles, so it is not timed. An escape out of that scope relocates every boxed descendant recursively and returns each old block (memory.md §3.5); nothing inside a roaming owner is referenced, so nothing else is updated. A value copy reallocates every payload so the two share no storage (§2.3); fresh construction builds each node in place and copies nothing.", "meta": [ ("Tree", "complete binary, depth 12 — 8,191 nodes"), - ("Hosted node", "16B — value and two handles"), + ("Owned node", "16B — value and two handles"), ("Value node", "16B — value and two handles"), ("Boxed payload", "exact node size, node alignment; no size class, no floor"), ("Stack key", "resolved once from the member's type, not per allocation"), ("Escape", "recursive relocation; old blocks returned to their exact-size stacks"), ("Deep copy", "recursive; source keeps its own storage"), ("Fresh construction", "built directly in the destination — no copy"), - ("Asserted", "overwriting a settled boxed member, and the boxed member inside it, reuses both blocks; guests to either keep their address and see the replacement"), + ("Asserted", "overwriting a settled boxed member, and the boxed member inside it, reuses both blocks; references to either keep their address and see the replacement"), ("Runs", "20 — median reported"), ], }, @@ -247,7 +247,7 @@ def get_color(impl_name): for key, color in DYN_COLORS: if lower.startswith(key): return color - if "zane" in lower or "segmented" in lower or "hosted tree" in lower: + if "zane" in lower or "segmented" in lower or "owned tree" in lower: for key, color in ZANE_VARIANTS.items(): if key in lower: return color @@ -260,9 +260,9 @@ def get_color(impl_name): if impl_name.startswith(prefix): return color - if lower.startswith("hosted array shards, concurrent"): + if lower.startswith("owned array shards, concurrent"): return "#7c6ff7" - if lower.startswith("hosted array shards, sequential"): + if lower.startswith("owned array shards, sequential"): return "#3aab76" if "sequential" in lower: return "#c49a2a" diff --git a/bench/explanations.txt b/bench/explanations.txt index 4e8e50d..965e7cd 100644 --- a/bench/explanations.txt +++ b/bench/explanations.txt @@ -4,7 +4,7 @@ # zane_bench_results.json, and zane_bench_results.txt rendered from it). # # PROVENANCE AND NOISE. The committed run is a fresh full-suite run of the -# settled/roaming harness, the first since the anchor pool, the per-host +# settled/roaming harness, the first since the anchor pool, the per-object # backpointer and forwarding were removed (spec PR #212). It was taken on a # different and less powerful machine than the previous pin, which ran under # WSL2 with no core pinning on a hybrid desktop CPU of 8 performance and 16 @@ -42,8 +42,8 @@ Zane's bump (~86us) runs ~58x faster than malloc (~5.03ms) and ~6.5x faster than Pool (~559us), and since only Zane's release is free, its figure is allocation alone where the other two carry their frees inside it. Neither -Zane nor the flat arena (~39us) writes to the memory it hands out now that a -host carries no backpointer, so the ~2.2x between them is not cache-line +Zane nor the flat arena (~39us) writes to the memory it hands out now that an +object carries no backpointer, so the ~2.2x between them is not cache-line fills: the only code that differs is Zane's chunk-boundary check and the frontier it keeps in its region record. The first of Zane's twenty passes took ~1.14ms, 13x its median; the harness touches every page of the region @@ -51,7 +51,7 @@ before any test runs, so that pass is warm-up of something other than page faults, and the median is the figure to read. [Test 2] -Zane's row is not a fast release loop; it is no loop at all. zm_host_release +Zane's row is not a fast release loop; it is no loop at all. zm_own_release is an empty function, so the compiler deletes the 100k-iteration loop outright, and the harness reports the row as eliminated rather than timing two timestamps around nothing. malloc (~2.27ms) and Pool (~681us) spend ~23ns and @@ -83,12 +83,12 @@ call it a win. Bounding a block to one chunk is what forces relocation, and it stays cheap because only the two oversized spans copy at all. [Test 6] -A guest now costs what a pointer costs. Resolving a segmented offset (~195us +A reference now costs what a pointer costs. Resolving a segmented offset (~195us with the chunk directory hoisted, ~168us re-fetched) ties dereferencing a raw pointer (~191us); the fastest passes, ~157us direct against ~163-166us offset, agree to within 6%, and the re-fetched row coming in ahead is noise. Under anchors the same test cost ~1.24x direct, which was the anchor-cell -load a guest no longer makes. The shift and mask that turn an offset into an +load a reference no longer makes. The shift and mask that turn an offset into an address are absorbed by the pipeline. [Test 7] @@ -115,7 +115,7 @@ A bump frontier cannot be fragmented, and the refill shows it: Zane takes across all 20 passes. It is also the one row in the suite that got faster than the previous pin in absolute terms, ~77us then, on a run where every unchanged row got slower: refilling no longer zeroes a backpointer in each -host. malloc (~2.28ms) and Pool (~1.19ms) refill from freed slots at ~46x and +object. malloc (~2.28ms) and Pool (~1.19ms) refill from freed slots at ~46x and ~24x, and both degrade across the run: Pool's passes climb from ~379us to a ~2.24ms worst and malloc's from ~1.49ms to ~3.01ms, each cycle handing the free list back in a worse order than it found it. @@ -125,8 +125,8 @@ Teardown of the branching tree: Zane ~62us, Pool ~79us (~1.3x), malloc ~152us (~2.5x). This is the first pin on the corrected topology, roughly 1,070 branch points at depth ~20-26 rather than the 4,000-node chain the earlier figures measured, and the cross-allocator ordering survived the -change. Guest density no longer has a row, because a guest leaves nothing in -its host for teardown to visit; what Zane's walk does per node is return the +change. Reference density no longer has a row, because a reference leaves +nothing in its owner for teardown to visit; what Zane's walk does per node is return the node's child list to its exact-size stack. [Test 11] diff --git a/bench/zane_bench.c b/bench/zane_bench.c index 6fb71cc..6162918 100644 --- a/bench/zane_bench.c +++ b/bench/zane_bench.c @@ -225,7 +225,7 @@ static void workers_shutdown(void) { #define ZD_NIL 0xFFFFFFFFu #define ZD_STACKS 1024 -typedef uint32_t ZGuest; +typedef uint32_t ZRef; typedef struct { uint32_t chunk; uint32_t off; int live; uint8_t *cbase; } ZBump; typedef struct { uint32_t size; uint32_t align; uint32_t head; } ZSizeStack; @@ -353,11 +353,11 @@ static int zd_grow_in_place(void *block, size_t old_bytes, size_t new_bytes) { return 1; } -static inline void *zm_host(size_t obj_size) { return zm_fixed_alloc(obj_size); } -static void zm_host_release(void *obj, size_t obj_size) { (void)obj; (void)obj_size; } +static inline void *zm_own(size_t obj_size) { return zm_fixed_alloc(obj_size); } +static void zm_own_release(void *obj, size_t obj_size) { (void)obj; (void)obj_size; } -static inline ZGuest zm_mint_guest(void *obj) { return zm_seg(obj); } -static inline void *zm_deref(ZGuest g) { return zm_resolve(g); } +static inline ZRef zm_mint_ref(void *obj) { return zm_seg(obj); } +static inline void *zm_deref(ZRef r) { return zm_resolve(r); } static void zm_reset(void) { zm.next_chunk = 0; @@ -518,7 +518,7 @@ static void test1(void) { double T[RUNS]; void **ptrs = (void**)malloc(N * sizeof(void *)); - for (int r=0;rhp = i%100+1; - refs[i] = zm_mint_guest(objs[i]); + refs[i] = zm_mint_ref(objs[i]); direct[i] = objs[i]; } @@ -732,7 +732,7 @@ static void test6(void) { record_row("Segmented offset (chunk dir reloaded)", T); for(int i=0;islots[i]){p->slots[i]=NULL;p->c typedef void*(*AllocFn)(size_t); typedef void (*FreeFn)(void*,size_t); -static void *zane_obj_alloc(size_t s){return zm_host(s);} -static void zane_obj_free (void*p,size_t s){zm_host_release(p,s);} +static void *zane_obj_alloc(size_t s){return zm_own(s);} +static void zane_obj_free (void*p,size_t s){zm_own_release(p,s);} static void *ma_obj_alloc(size_t s){return malloc(s);} static void ma_obj_free (void*p,size_t s){(void)s;free(p);} static void *po_obj_alloc(size_t s){return pool_alloc(s);} @@ -938,10 +938,10 @@ static void test9(void) { for(int r=0;rhp=i;} - for(int i=0;ihp=i;} + for(int i=0;inchildren;i++) destroy_zane(n->children[i]); if(n->children) zane_children_free(n->children,n->nchildren); - zm_host_release(n, sizeof(TNode)); + zm_own_release(n, sizeof(TNode)); } static void destroy_malloc(TNode *n){ if(!n)return; for(int i=0;inchildren;i++) destroy_malloc(n->children[i]); if(n->children)ma_children_free(n->children,n->nchildren); free(n); } @@ -1026,7 +1026,7 @@ static void destroy_pool(TNode *n) { if(!n)return; for(int i=0;inchildren;i static void test10(void) { - record_test("Test 10", "Hosting tree teardown [~4000 nodes, cascade post-order destroy]"); + record_test("Test 10", "Ownership tree teardown [~4000 nodes, cascade post-order destroy]"); double T[RUNS]; zane_children_stack = zd_stack(ZM_LIST_MIN, ZM_LINE); @@ -1221,23 +1221,23 @@ static void test12(void) { double T[RUNS]; assert((N % WORKER_COUNT) == 0); - Entity *hosted = (Entity*)malloc(N * sizeof(Entity)); + Entity *owned = (Entity*)malloc(N * sizeof(Entity)); for (int i = 0; i < N; i++) { - hosted[i].id = i; - hosted[i].x = i * 1.1; - hosted[i].y = i * 2.2; - hosted[i].hp = i % 100 + 1; + owned[i].id = i; + owned[i].x = i * 1.1; + owned[i].y = i * 2.2; + owned[i].hp = i % 100 + 1; } const int shard_len = N / WORKER_COUNT; const int64_t expected = (int64_t)(N / 100) * 5050; - { int64_t warm = 0; for (int i = 0; i < N; i++) warm += hosted[i].hp; assert(warm == expected); sink ^= warm; } + { int64_t warm = 0; for (int i = 0; i < N; i++) warm += owned[i].hp; assert(warm == expected); sink ^= warm; } { WorkerJob run[WORKER_COUNT]; SumJob jobs[WORKER_COUNT]; for (int i = 0; i < WORKER_COUNT; i++) { - jobs[i] = (SumJob){ .base = hosted, .start = i * shard_len, .len = shard_len, .sum = 0 }; + jobs[i] = (SumJob){ .base = owned, .start = i * shard_len, .len = shard_len, .sum = 0 }; run[i] = (WorkerJob){ .fn = sum_entity_shard, .arg = &jobs[i] }; } workers_run(run, WORKER_COUNT); @@ -1254,20 +1254,20 @@ static void test12(void) { double t0 = now_ns(); for (int shard = 0; shard < WORKER_COUNT; shard++) { int start = shard * shard_len; - for (int i = 0; i < shard_len; i++) acc += hosted[start + i].hp; + for (int i = 0; i < shard_len; i++) acc += owned[start + i].hp; } T[r] = now_ns() - t0; assert(acc == expected); sink ^= acc; } - record_row("Hosted Array shards, sequential", T); + record_row("Owned Array shards, sequential", T); for (int r = 0; r < RUNS; r++) { WorkerJob run[WORKER_COUNT]; SumJob jobs[WORKER_COUNT]; double t0 = now_ns(); for (int i = 0; i < WORKER_COUNT; i++) { - jobs[i] = (SumJob){ .base = hosted, .start = i * shard_len, .len = shard_len, .sum = 0 }; + jobs[i] = (SumJob){ .base = owned, .start = i * shard_len, .len = shard_len, .sum = 0 }; run[i] = (WorkerJob){ .fn = sum_entity_shard, .arg = &jobs[i] }; } workers_run(run, WORKER_COUNT); @@ -1279,9 +1279,9 @@ static void test12(void) { assert(acc == expected); sink ^= acc; } - record_row("Hosted Array shards, concurrent (4 workers)", T); + record_row("Owned Array shards, concurrent (4 workers)", T); - free(hosted); + free(owned); } #define REUSE_BLOCKS 2000 @@ -1488,7 +1488,7 @@ static void test14(void) { T[r] = now_ns() - t0; assert(bh_sum(root) == expected); sink ^= root; } - record_row("Escape roaming hosted tree (relocate)", T); + record_row("Escape roaming owned tree (relocate)", T); for (int r = 0; r < RUNS; r++) { zm_reset(); @@ -1523,10 +1523,10 @@ static void test14(void) { { ZSizeStack *es = zd_stack(sizeof(SEngine), 8); ZSizeStack *ts = zd_stack(sizeof(STurbo), 8); - uint32_t *car_engine = (uint32_t*)zm_host(sizeof(uint32_t)); + uint32_t *car_engine = (uint32_t*)zm_own(sizeof(uint32_t)); *car_engine = se_build(es, ts, 1, 10); - ZGuest engine = *car_engine; - ZGuest turbo = ((SEngine*)zm_deref(engine))->turbo; + ZRef engine = *car_engine; + ZRef turbo = ((SEngine*)zm_deref(engine))->turbo; uint32_t spare = se_build(es, ts, 2, 20); uint32_t spare_turbo = ((SEngine*)zm_resolve(spare))->turbo; se_overwrite(es, ts, *car_engine, spare); diff --git a/bench/zane_bench_results.json b/bench/zane_bench_results.json index fef75cf..8ed60da 100644 --- a/bench/zane_bench_results.json +++ b/bench/zane_bench_results.json @@ -281,7 +281,7 @@ }, { "id": "Test 4", - "title": "Iteration: inline (hosted) vs pointer-chase [32B Entity x 100k]", + "title": "Iteration: inline (owned) vs pointer-chase [32B Entity x 100k]", "rows": [ { "label": "Inline array (Array)", @@ -518,7 +518,7 @@ }, { "id": "Test 6", - "title": "Guest access via segmented offset vs direct pointer [100k accesses]", + "title": "Reference access via segmented offset vs direct pointer [100k accesses]", "rows": [ { "label": "Direct pointer (baseline)", @@ -867,7 +867,7 @@ }, { "id": "Test 10", - "title": "Hosting tree teardown [~4000 nodes, cascade post-order destroy]", + "title": "Ownership tree teardown [~4000 nodes, cascade post-order destroy]", "rows": [ { "label": "Zane cascade destroy", @@ -1032,7 +1032,7 @@ "title": "Concurrent shard scan [4 x Array read-only shard sums]", "rows": [ { - "label": "Hosted Array shards, sequential", + "label": "Owned Array shards, sequential", "samples_ns": [ 131561.0, 114527.0, @@ -1057,7 +1057,7 @@ ] }, { - "label": "Hosted Array shards, concurrent (4 workers)", + "label": "Owned Array shards, concurrent (4 workers)", "samples_ns": [ 450859.0, 422161.0, @@ -1219,7 +1219,7 @@ "title": "Boxed members: roaming escape vs deep value copy [8191 nodes]", "rows": [ { - "label": "Escape roaming hosted tree (relocate)", + "label": "Escape roaming owned tree (relocate)", "samples_ns": [ 85598.0, 84161.0, diff --git a/bench/zane_bench_results.txt b/bench/zane_bench_results.txt index aa6f22f..43749bc 100644 --- a/bench/zane_bench_results.txt +++ b/bench/zane_bench_results.txt @@ -28,7 +28,7 @@ Pool (per-size free-list) median 4588564.00 ns min 2950570.00 ns max 8647932.00 ns ( 4588.564 us) +--------------------------------------------------------------------------------------------------+ - | Test 4 -- Iteration: inline (hosted) vs pointer-chase [32B Entity x 100k] | + | Test 4 -- Iteration: inline (owned) vs pointer-chase [32B Entity x 100k] | +--------------------------------------------------------------------------------------------------+ Inline array (Array) median 126731.00 ns min 123431.00 ns max 224325.00 ns ( 126.731 us) Pointer array, sequential median 204294.00 ns min 188414.00 ns max 275812.00 ns ( 204.294 us) @@ -45,7 +45,7 @@ CChunked (chunk=64, ptr-array) median 703012.00 ns min 614467.00 ns max 1058061.00 ns ( 703.012 us) +--------------------------------------------------------------------------------------------------+ - | Test 6 -- Guest access via segmented offset vs direct pointer [100k accesses] | + | Test 6 -- Reference access via segmented offset vs direct pointer [100k accesses] | +--------------------------------------------------------------------------------------------------+ Direct pointer (baseline) median 190864.00 ns min 157134.00 ns max 272404.00 ns ( 190.864 us) Segmented offset (chunk dir cached) median 194718.00 ns min 166223.00 ns max 307789.00 ns ( 194.718 us) @@ -74,7 +74,7 @@ Pool -- refill from free-list median 1189349.00 ns min 379128.00 ns max 2237458.00 ns ( 1189.349 us) +--------------------------------------------------------------------------------------------------+ - | Test 10 -- Hosting tree teardown [~4000 nodes, cascade post-order destroy] | + | Test 10 -- Ownership tree teardown [~4000 nodes, cascade post-order destroy] | +--------------------------------------------------------------------------------------------------+ Zane cascade destroy median 61770.00 ns min 60185.00 ns max 139085.00 ns ( 61.770 us) malloc cascade destroy median 151595.00 ns min 132747.00 ns max 2204337.00 ns ( 151.595 us) @@ -90,8 +90,8 @@ +--------------------------------------------------------------------------------------------------+ | Test 12 -- Concurrent shard scan [4 x Array read-only shard sums] | +--------------------------------------------------------------------------------------------------+ - Hosted Array shards, sequential median 113699.50 ns min 88976.00 ns max 241818.00 ns ( 113.700 us) - Hosted Array shards, concurrent (4 workers) median 397919.50 ns min 248006.00 ns max 647310.00 ns ( 397.920 us) + Owned Array shards, sequential median 113699.50 ns min 88976.00 ns max 241818.00 ns ( 113.700 us) + Owned Array shards, concurrent (4 workers) median 397919.50 ns min 248006.00 ns max 647310.00 ns ( 397.920 us) +--------------------------------------------------------------------------------------------------+ | Test 13 -- Dynamic-region block churn [10 rounds x 2k blocks x 128/256/512B] | @@ -105,7 +105,7 @@ +--------------------------------------------------------------------------------------------------+ | Test 14 -- Boxed members: roaming escape vs deep value copy [8191 nodes] | +--------------------------------------------------------------------------------------------------+ - Escape roaming hosted tree (relocate) median 84027.50 ns min 83354.00 ns max 106732.00 ns ( 84.028 us) + Escape roaming owned tree (relocate) median 84027.50 ns min 83354.00 ns max 106732.00 ns ( 84.028 us) Deep-copy value tree (place source) median 80871.00 ns min 71432.00 ns max 86791.00 ns ( 80.871 us) Construct fresh value tree in place median 45724.00 ns min 45459.00 ns max 46373.00 ns ( 45.724 us) malloc deep copy median 341561.50 ns min 284774.00 ns max 813260.00 ns ( 341.562 us) diff --git a/contributing/naming-terms.md b/contributing/naming-terms.md index 3187cd4..dfe9930 100644 --- a/contributing/naming-terms.md +++ b/contributing/naming-terms.md @@ -2,7 +2,7 @@ This guide describes how the spec chooses the coined terms it reuses — the named concepts recorded in [`glossary.md`](../spec/glossary.md), such as `verb`, -`subject`, `mould`, `borrow`, `host`, `guest`, `anchor`, and `tether`. It governs the *terms of art* the +`subject`, `mould`, `borrow`, `settled`, and `roaming`. It governs the *terms of art* the documentation leans on, not the surface keywords of the language itself. Terminology is worth naming deliberately because a good term is used on nearly @@ -28,15 +28,9 @@ does the teaching before the definition is even read. is cast from them. - **`borrow`** — the passing mode for a value type. The callee is *lent* the caller's storage for the call and must give it back; it cannot keep it. -- **`host` / `guest`** — the source-facing relationship. A host provides a - reference-type object and bounds a guest's stay; a guest may access what the - host provides without storing it or controlling its lifetime. -- **`anchor` / `tether`** — the internal memory-model mechanism. A guest's - tether resolves through the hosted object's anchor, and rehosting updates the - anchor rather than every tether. - -Keeping the pairs in separate registers matters: host/guest teaches what source -code means, while anchor/tether explains how the runtime preserves that meaning. +- **`settled` / `roaming`** — the two states of an owner. A settler has stopped + travelling and taken a fixed home, so a reference may name it; a roaming owner + is still on the move, and nothing may reference it. In each case the everyday meaning is not decoration — it is a true structural analogy. The word's real-world role maps onto the concept's role, so the name @@ -72,8 +66,8 @@ reader has to sound out, is a poor handle no matter how precise. ### 2.4 An oblique connection is fine The link between the word and the concept may be one hop away; it need not -encapsulate the definition. A name is not a summary. `anchor` does not spell out -"stable indirection through an anchor table" — it just points, and the meaning +encapsulate the definition. A name is not a summary. `settled` does not spell out +"may be referenced, and never moved again" — it just points, and the meaning settles onto it with use. Aim for *connected but not descriptive*. ### 2.5 The meaning accrues through use @@ -83,6 +77,23 @@ little empty at first and fills up as the spec uses it. The best connections are the ones a reader discovers *after* the word already feels natural — the buried resonance that rewards a second look rather than announcing itself. +### 2.6 It takes every form the prose needs + +A term that names a relationship is needed as a verb as often as a noun: the +rules say what *may* stand in that relationship, not only what the thing is +called. Check that the word bends into every form the spec will write — noun, +verb, participle, and the compounds built from them — before adopting it. A +noun with no natural verb pushes the prose into forced coinages ("may be +guested") or into a generic word that drifts from the term. + +### 2.7 It carries the weight of the rule + +A term whose concept carries an obligation should be a word whose everyday sense +already carries it. A reader brings the weight of a familiar word along: one who +meets a *reference* already expects that it can dangle and must not outlive what +it names, while a *link* reads as something you may follow or ignore. A word +lighter than its rule teaches the reader to take the rule lightly. + --- ## 3. The Test @@ -107,8 +118,8 @@ seen rarely and gains its meaning slowly, so an oblique reference like *Ariadne* (the thread through the labyrinth) is a strength. A **term** is the opposite case: read constantly, and needed to teach on contact. -Terms therefore lean plain and everyday — `verb`, `subject`, `mould`, `borrow`, `host`, -`guest`, `anchor`, `tether` — even when the underlying instinct (name by metaphor, keep the link +Terms therefore lean plain and everyday — `verb`, `subject`, `mould`, `borrow`, `settled`, +`roaming` — even when the underlying instinct (name by metaphor, keep the link oblique) is the same. When in doubt for a term, choose the ordinary word over the exotic one. --- diff --git a/contributing/writing-spec-docs.md b/contributing/writing-spec-docs.md index 1f7130b..907157d 100644 --- a/contributing/writing-spec-docs.md +++ b/contributing/writing-spec-docs.md @@ -77,7 +77,7 @@ Always `Zane` followed by a descriptive noun phrase. Examples: One or two sentences immediately after the title, before the first `---`. Describes in plain English what the document covers. No heading — just body text. ```markdown -This document specifies Zane's memory model: how objects are hosted and destroyed, how memory is laid out and allocated, and how non-hosting references are safely tracked through the anchor system. +This document specifies Zane's memory model: how objects are owned and destroyed, how memory is laid out and allocated, and how references are kept from dangling. ``` ### 2.3 See also block @@ -143,8 +143,8 @@ Every topic document begins with `## 1. Overview`. It contains: Zane uses a **structural effect model** with a single user-facing effect modifier: `mut`. -- **Single hosting.** Every heap object has exactly one host at all times. -- **Host and guest.** An object lives in a host; an `&` guest may access it without controlling its lifetime. +- **Single ownership.** Every heap object has exactly one owner at all times. +- **Owner and reference.** An object is held by an owner; an `&` reference may access it without controlling its lifetime. ``` The Overview is orientation, not rationale: it says what the feature *is*, not why it was chosen over the alternatives. If one of the core ideas is non-obvious, name it here in one line and point to the stories doc for the argument. @@ -320,8 +320,8 @@ Void[Int, this Node] // ILLEGAL: this must be the first parameter Keep sentences short. One idea per sentence. Avoid nested clauses. Use active voice. -Good: *A guest never outlives its host.* -Bad: *The lifetime associated with a guest is prevented from extending beyond the lifetime associated with its host.* +Good: *A reference never outlives its owner.* +Bad: *The lifetime associated with a reference is prevented from extending beyond the lifetime associated with its owner.* ### 6.2 Emphasis @@ -357,6 +357,10 @@ At the end of a section that is closely connected to another document, add a `> Do not frame a rule in terms of backward compatibility or migrating existing code. The toolchain versions itself (see [`dependencies.md`](../spec/dependencies.md) §14), so code keeps compiling against the version it was written for; the spec describes only the language as it is now, never a migration path from an older form. +### 6.6 Name an `&` slot by its sigil + +Call a field, element, or parameter declared `&T` an **`&` field**, an **`&` element**, or an **`&T` parameter**. Do not call it a "reference field" or "reference parameter". A *reference-type* field or parameter is one whose type is a reference type, and it owns or borrows its object, so a single hyphen would be all that separates owning from not owning. + --- ## 7. Adding a New Spec Document diff --git a/contributing/writing-stories-docs.md b/contributing/writing-stories-docs.md index c56f624..dabcdb4 100644 --- a/contributing/writing-stories-docs.md +++ b/contributing/writing-stories-docs.md @@ -63,7 +63,7 @@ The test is to read the heading without the chapter. A reader who knows the spec Teasers fail in a few recurring ways: -- **A withheld subject.** "The *X* that *Y*", where *X* is a generic noun — the keyword, the word, the check, the ban, the host — and *Y* a riddle about it. The chapter knows which one it means; the heading names it. +- **A withheld subject.** "The *X* that *Y*", where *X* is a generic noun — the keyword, the word, the check, the ban, the owner — and *Y* a riddle about it. The chapter knows which one it means; the heading names it. - **A metaphor standing in for the subject.** A chapter may build its argument on an image, but the heading names the thing the image stands for, since a reader meets the heading before the image is explained. - **Personification.** A heading in which a construct *wanted*, *outran*, *outlived*, or *could not contain itself* treats a rule as a character. State what the rule does. - **A paradox or punchline** that inverts an expectation the reader has not formed yet. State the outcome instead of staging a surprise. @@ -104,9 +104,9 @@ Let the length flex with the episode: a minor turn is a paragraph, a foundationa ### 3.1 Coined terms are defended here -When the topic **coins, renames, or reserves a term of art** — one that earns a [`glossary.md`](../spec/glossary.md) entry (`verb`, `mould`, `host`, `guest`, `anchor`, `tether`, and their kin) — the story is where its name is argued, and this is a requirement, not an optional flourish. The glossary records only the short "why this name"; the developed case — the candidates weighed, why each rival lost, why the winner won — is design history like any other decision, and it belongs in the chapter that introduces the concept the term names, told as prose (see [`naming-terms.md`](naming-terms.md) §6). A term whose name was a genuine choice is not fully recorded until that choice is defended in a story. The one exception is a term whose name is self-evident — its everyday sense maps straight onto the concept, with no rival to reject — which needs no such passage. +When the topic **coins, renames, or reserves a term of art** — one that earns a [`glossary.md`](../spec/glossary.md) entry (`verb`, `mould`, `subject`, `settled`, `roaming`, and their kin) — the story is where its name is argued, and this is a requirement, not an optional flourish. The glossary records only the short "why this name"; the developed case — the candidates weighed, why each rival lost, why the winner won — is design history like any other decision, and it belongs in the chapter that introduces the concept the term names, told as prose (see [`naming-terms.md`](naming-terms.md) §6). A term whose name was a genuine choice is not fully recorded until that choice is defended in a story. The one exception is a term whose name is self-evident — its everyday sense maps straight onto the concept, with no rival to reject — which needs no such passage. -When a term is **renamed**, do not back-date the old name out of earlier chapters. The old chapters were written when the old name was true, and that history stands; open a *new* chapter (or extend the relevant one) that records the change and why it came — the way [`stories/memory.md`](../stories/memory.md) added a chapter for the host/guest rename rather than rewriting the chapters that still say "owner" and "tether". This is the [append-don't-overwrite](#5-updating-a-story-when-the-spec-changes) rule applied to vocabulary. +When a term is **renamed**, do not back-date the old name out of earlier chapters. The old chapters were written when the old name was true, and that history stands; open a *new* chapter (or extend the relevant one) that records the change and why it came — the way [`stories/memory.md`](../stories/memory.md) added a chapter for each renaming of `&` — ref, tether, guest, reference — rather than rewriting the chapters that still say the older words. This is the [append-don't-overwrite](#5-updating-a-story-when-the-spec-changes) rule applied to vocabulary. --- diff --git a/spec/adt.md b/spec/adt.md index 65d65f9..9588ab1 100644 --- a/spec/adt.md +++ b/spec/adt.md @@ -15,7 +15,7 @@ Zane separates two ideas that other languages often merge. An `enum` is a closed - **`The # axis applies to the sum mould`.** A plain `variant` is its value form; `#variant` is its reference form (see [`types.md`](types.md) §2.1). The `#` mark applies the same way to an `enum`. - **`Reading a variant member is partial`.** A case may not be live, so a member read is abortable. The primary consumer is exhaustive dispatch. - **`A variant is matched in one central block`.** A `match` block (§5) dispatches a variant on its live tag — variant matching, not pattern matching: no nested destructuring, guards, or shape tests — and must cover every case, with no default arm. -- **`Either sum may recurse`.** A recursive member is an ordinary member the compiler **boxes** — a fixed-size handle inline, payload in the dynamic region (§4). Boxing is placement, not a change of type, so both forms may lead back to themselves: in a `#variant` the boxed member **hosts** its child, and in a `variant` the value owns its child outright and copies it deeply (see [`memory.md`](memory.md) §2.3). What a value sum still cannot carry is a reference-type or `&` payload. +- **`Either sum may recurse`.** A recursive member is an ordinary member the compiler **boxes** — a fixed-size handle inline, payload in the dynamic region (§4). Boxing is placement, not a change of type, so both forms may lead back to themselves: in a `#variant` the boxed member **owns** its child, and in a `variant` the value owns its child outright and copies it deeply (see [`memory.md`](memory.md) §2.3). What a value sum still cannot carry is a reference-type or `&` payload. --- @@ -50,10 +50,10 @@ A plain `variant` is a **value** sum: copied on assignment and transitively valu ```zane type Countdown = variant { done Unit; more Countdown; } // legal: `more` is a boxed member the value owns -type Chain = #variant { done Unit; more Chain; } // legal: `more` is a boxed hosting member +type Chain = #variant { done Unit; more Chain; } // legal: `more` is a boxed owning member ``` -A `Countdown` is copied whole, so copying one allocates and copies every node beneath it (see [`memory.md`](memory.md) §2.3), and two `Countdown` values never share a node. A `Chain` has identity and is **moved** rather than copied, and its boxed member hosts the child it holds. The choice between them is therefore the ordinary value/reference choice, made on the ordinary grounds, and recursion no longer forces it. +A `Countdown` is copied whole, so copying one allocates and copies every node beneath it (see [`memory.md`](memory.md) §2.3), and two `Countdown` values never share a node. A `Chain` has identity and is **moved** rather than copied, and its boxed member owns the child it holds. The choice between them is therefore the ordinary value/reference choice, made on the ordinary grounds, and recursion no longer forces it. > **Story:** [`stories/adt.md`](../stories/adt.md#a-value-sum-may-recurse) — "A value sum may recurse". @@ -79,11 +79,11 @@ type Expr = #variant { } ``` -`left`, `right`, `flip`, and `parenthesized` are ordinary **hosting** members: an `Operation` owns the two `Expr` nodes it holds, and an `Expr.flip` owns the `Expr` under it. Because their types lead back to the enclosing type, the compiler boxes them so every type here stays finite (§4). No `&` appears, and none is required. +`left`, `right`, `flip`, and `parenthesized` are ordinary **owning** members: an `Operation` owns the two `Expr` nodes it holds, and an `Expr.flip` owns the `Expr` under it. Because their types lead back to the enclosing type, the compiler boxes them so every type here stays finite (§4). No `&` appears, and none is required. A member projected as a type is written `Expr.intLit`: `Expr` is the type (uppercase) and `.intLit` is member selection (lowercase), exactly like `vec.x`. -Reading a member of a variant value is **partial**: the case may not be the live one. A member read is therefore an **abortable** access (`?` / `??`, see [`error-handling.md`](error-handling.md)). A variant member projection is not a guest source: a payload is always roaming, because the variant may stay alive while its live case changes and the old payload disappears ([`memory.md`](memory.md) §2.8.1). Code that needs durable access takes a guest to the whole reference variant and performs the case read through that guest when needed (see [`memory.md`](memory.md) §2.8). The primary consumer of a variant is the exhaustive `match` block (§5). A single-payload case, once bound, behaves as its payload, so a value of `Expr.intLit`'s payload type reaches that payload's members directly. +Reading a member of a variant value is **partial**: the case may not be the live one. A member read is therefore an **abortable** access (`?` / `??`, see [`error-handling.md`](error-handling.md)). A variant member projection is not a reference source: a payload is always roaming, because the variant may stay alive while its live case changes and the old payload disappears ([`memory.md`](memory.md) §2.8.1). Code that needs durable access takes a reference to the whole reference variant and performs the case read through that reference when needed (see [`memory.md`](memory.md) §2.8). The primary consumer of a variant is the exhaustive `match` block (§5). A single-payload case, once bound, behaves as its payload, so a value of `Expr.intLit`'s payload type reaches that payload's members directly. > **Story:** [`stories/adt.md`](../stories/adt.md#a-variant-payload-is-not-a-guest-source) — "A variant payload is not a guest source". @@ -130,13 +130,13 @@ Naming a case takes its payload whole; to reach a nested case, write another cas An `enum` member is the payloadless degenerate of the same form: `Colors.red` selects a case that carries no payload, so it is written with no argument list (§2). A payload-carrying case is called; a payloadless one is selected. -A recursive `#variant` case carries a **hosting** payload (§4), so it is constructed like any other host: `Expr.flip(inner)` takes an `Expr`, and its argument must be a move-source (see [`lifetimes.md`](lifetimes.md) §1.2). A constructor result is one, so a whole tree may be written as a single nested expression: +A recursive `#variant` case carries an **owning** payload (§4), so it is constructed like any other owning value: `Expr.flip(inner)` takes an `Expr`, and its argument must be a move-source (see [`lifetimes.md`](lifetimes.md) §1.2). A constructor result is one, so a whole tree may be written as a single nested expression: ```zane program Expr = Expr.op(Operation(Expr.intLit("3"), Expr.intLit("2"), Operator.add)); ``` -No guest source is needed anywhere, because no guest is involved: each case takes hosting of the node it is given. +No reference source is needed anywhere, because no reference is involved: each case takes ownership of the node it is given. A recursive **value** sum is built the same way on the surface and needs even less: its payload is copied, not moved, so any expression of the payload type will do and the move-source rule never comes up. `Countdown.more(n)` deep-copies `n` into the new node's boxed payload (see [`memory.md`](memory.md) §2.3), leaving `n` untouched and independently usable. @@ -172,11 +172,11 @@ type Expr = #variant { op Operation; intLit String; } program Expr = Expr.op(Operation(Expr.intLit("3"), Expr.intLit("2"), Operator.add)); ``` -- **A recursive member owns its child.** `left` and `right` own the `Expr` nodes they hold, and those nodes are destroyed when the `Operation` is. In a reference type that ownership is **hosting**: the structure is a hosting tree, rooted wherever its outermost node is hosted — a local, a field, or a container slot — and it is moved and destroyed whole, like any other hosting subtree (see [`lifetimes.md`](lifetimes.md) §1.2). In a value type it is ordinary value ownership: the tree is copied when the value is copied and freed when the value dies (see [`memory.md`](memory.md) §2.3). -- **Boxing is required on a cycle and permitted off one.** The graph this rule reads is one of **owning** edges — members that store an instance of their type, inline or boxed. An `&` member is not one of them: a guest is fixed-size storage whatever it points at, so a cycle closed through `&` needs no boxing and never triggers this rule. A member **MUST** be boxed when its declared type can lead back to the enclosing type along owning edges — when its edge lies on a cycle in that graph — because no finite inline layout exists for it. Every such edge is boxed, so nothing depends on declaration order or on choosing where to cut the cycle: above, `Expr.op`, `Operation.left`, and `Operation.right` are all boxed, and every type on the cycle has a finite, statically known size. Off a cycle, an implementation **MAY** box or inline as it judges best. Inline is the ordinary choice, but a sum whose widest case dwarfs its common ones is the case an implementation may want to place out of line, and nothing here forecloses that. The choice is made per type, not per instance, so every value of a type is still the same size and uniform stride holds (see [`generics.md`](generics.md) §7); and a program cannot observe which was chosen, because placement is not a language-visible property (see [`memory.md`](memory.md) §3.5) and no operator exposes a type's footprint. +- **A recursive member owns its child.** `left` and `right` own the `Expr` nodes they hold, and those nodes are destroyed when the `Operation` is. In a reference type that ownership is held by **owners**: the structure is an ownership tree, rooted wherever its outermost node is owned — a local, a field, or a container slot — and it is moved and destroyed whole, like any other ownership subtree (see [`lifetimes.md`](lifetimes.md) §1.2). In a value type it is ordinary value ownership: the tree is copied when the value is copied and freed when the value dies (see [`memory.md`](memory.md) §2.3). +- **Boxing is required on a cycle and permitted off one.** The graph this rule reads is one of **owning** edges — members that store an instance of their type, inline or boxed. An `&` member is not one of them: a reference is fixed-size storage whatever it points at, so a cycle closed through `&` needs no boxing and never triggers this rule. A member **MUST** be boxed when its declared type can lead back to the enclosing type along owning edges — when its edge lies on a cycle in that graph — because no finite inline layout exists for it. Every such edge is boxed, so nothing depends on declaration order or on choosing where to cut the cycle: above, `Expr.op`, `Operation.left`, and `Operation.right` are all boxed, and every type on the cycle has a finite, statically known size. Off a cycle, an implementation **MAY** box or inline as it judges best. Inline is the ordinary choice, but a sum whose widest case dwarfs its common ones is the case an implementation may want to place out of line, and nothing here forecloses that. The choice is made per type, not per instance, so every value of a type is still the same size and uniform stride holds (see [`generics.md`](generics.md) §7); and a program cannot observe which was chosen, because placement is not a language-visible property (see [`memory.md`](memory.md) §3.5) and no operator exposes a type's footprint. - **Nothing is written for it.** The programmer writes `left Expr`; the compiler boxes it because inline placement is impossible, exactly as it places list elements out of line. There is no `Box` type and no marker, because placement was never a language-visible property (see [`memory.md`](memory.md) §3.5). - **Both kinds may recurse.** A recursive value type is legal, because a box is placement rather than a reference-type field. The body syntax is symmetric across all four kinds, and so is recursion: `#` decides identity, aliasing, and copy-versus-move, not whether a type may contain itself. What a copy of a recursive value costs, and why it shares no node with its original, is the deep-copy rule (see [`memory.md`](memory.md) §2.3). -- **`&` is for aliasing, not for recursion.** A guest expresses non-hosting access — a parent back-pointer, a symbol table naming nodes, a genuine graph edge — and a cycle of guests is a shape hosting could not express in the first place. An `&` member follows the ordinary guest rules — it must be assigned from a guest source ([`memory.md`](memory.md) §2.8) naming a host whose owner outlives the owner of the member's root symbol, and every later store of the containing value asks that again ([`lifetimes.md`](lifetimes.md) §1.1); an owning member, boxed or not, is subject to neither. The owning edges this section describes are also the edges that walk finds an `&` along ([`lifetimes.md`](lifetimes.md) §1.10). +- **`&` is for aliasing, not for recursion.** A reference expresses non-owning access — a parent back-pointer, a symbol table naming nodes, a genuine graph edge — and a cycle of references is a shape ownership could not express in the first place. An `&` member follows the ordinary reference rules — it must be assigned from a reference source ([`memory.md`](memory.md) §2.8) naming an owner whose scope outlives the scope of the member's root symbol, and every later store of the containing value asks that again ([`lifetimes.md`](lifetimes.md) §1.1); an owning member, boxed or not, is subject to neither. The owning edges this section describes are also the edges that walk finds an `&` along ([`lifetimes.md`](lifetimes.md) §1.10). Boxing carries costs, all of them the price of the child actually being owned. Reaching a boxed child costs one indirection, which recursion cannot avoid. Moving a roaming reference-type node copies its handles and leaves its boxed descendants where they are; an escape out of the scope that holds them must leave every one in a scope that lives as long as the destination, and where the compiler did not allocate them there to begin with, relocating them costs time proportional to the tree rather than to its root (see [`memory.md`](memory.md) §3.5) — the same price a `List` pays for its backing store. A recursive **value** type pays that cost more often, because it is copied rather than moved — but only where a copy actually happens. Binding an existing **place** into another slot walks and reallocates the whole structure. Building a fresh one does not: a non-place result is constructed directly in its eventual destination, recursively, so `Countdown.more(Countdown.more(Countdown.done(Unit())))` builds each node once where it will live rather than copying each completed prefix into the next (see [`memory.md`](memory.md) §2.3). Passing one to a parameter does not either, since a value-type parameter is a read-only borrow (§2.9). The O(structure) case is the one that reads like a copy: `b Countdown = a`, or a field, container, or return store whose source is an **existing** value. A store whose source is a fresh non-place result constructs in place instead and costs nothing extra, returns included. Where a tree is large and shared handling is wanted, that is the signal to reach for the reference form. @@ -369,7 +369,7 @@ type Expr = #variant { intLit String; flip Expr; } // recursive sum: reference | Variant member read | Partial and therefore abortable; a single-payload case behaves as its payload once bound | | struct/variant symmetry | One body grammar; the keyword flips meaning, construction, read totality, and layout; the `#` modifier picks value versus reference | | `#variant` / `#enum` | `#variant` is the sum mould's reference form (identity, aliasable, moved); `#enum` is a reference cell holding a tag; the `#` modifier applies uniformly | -| Recursion | A recursive member is a member the compiler boxes — a fixed-size handle inline, payload in the dynamic region — so a recursive type owns its children; boxing is written nowhere and is available to **both** kinds: a `#variant`/`#struct` hosts its children and is moved, a `variant`/`struct` owns its children and deep-copies them | +| Recursion | A recursive member is a member the compiler boxes — a fixed-size handle inline, payload in the dynamic region — so a recursive type owns its children; boxing is written nowhere and is available to **both** kinds: a `#variant`/`#struct` owns its children and is moved, a `variant`/`struct` owns its children and deep-copies them | | Variant storage | `variant` is the sum mould's value form, laid out inline apart from any boxed member; `#variant` is its reference form, carrying a tag, and is placed by the reference-type rules | | `match` block | Expression legal in any expression position; `match scrutinee { [binder] selector => body; ... }`; one result type; runtime tag jump; static narrowing chooses statically; abort flows through with `?` | | Match arm | `[binder] selector => body`; binder optional; selector is a case or a `[ ]` group of cases; a `[ ]` group is shorthand for one arm per case, each binding its own case's payload; the bracket is a selector, not a type; for the whole variant an arm reads the scrutinee | diff --git a/spec/concurrency.md b/spec/concurrency.md index e7349a0..641a6e7 100644 --- a/spec/concurrency.md +++ b/spec/concurrency.md @@ -12,7 +12,7 @@ Zane separates **parallelism** (compiler-managed, unobservable) from **concurren - **`Implicit parallelism`.** The compiler may run provably independent work in parallel when it cannot change program results. - **`Explicit concurrency`.** `spawn` starts a concurrent function or method call; ordering is the programmer’s responsibility. -- **`Water-tower lifetimes`.** A scope’s hosted objects live until all spawned work in that scope completes. +- **`Water-tower lifetimes`.** Objects owned in a scope live until all spawned work in that scope completes. - **`Mutation needs a value subject`.** A spawned call may mutate only a value-typed subject; a value type's transitive alias-freedom lets the compiler rule out a data race from the subject's type, and at most one spawn may mutably borrow a given location. - **`No async coloring`.** Concurrency is chosen at the call site rather than encoded into function signatures. @@ -117,7 +117,7 @@ Independent work may still be parallelized only when doing so preserves those so ### 3.7 No serial-equivalence guarantee -`spawn` explicitly opts out of serial equivalence. Program results may depend on scheduling except where constrained by effect and hosting rules. +`spawn` explicitly opts out of serial equivalence. Program results may depend on scheduling except where constrained by effect and ownership rules. > **Story:** [`stories/concurrency.md`](../stories/concurrency.md#spawn-and-why-it-marks-only-a-call) — "`spawn`, and why it marks only a call". @@ -127,7 +127,7 @@ Independent work may still be parallelized only when doing so preserves those so ### 4.1 Water-tower lifetime extension -A scope does not complete until all `spawn`ed calls inside it have completed. Hosted objects in that scope are destroyed only when the scope is **drained**. +A scope does not complete until all `spawn`ed calls inside it have completed. Objects owned in that scope are destroyed only when the scope is **drained**. The analogy is a vertical water tower with water at the top and one horizontal plate for each still-running spawned call in that scope. The water cannot fall past a plate that is still in place, so destruction cannot pass that still-live concurrent work either. @@ -137,7 +137,7 @@ Each time one spawned call finishes, one plate is removed. The water level drops ### 4.2 Concurrent mutation requires a value-typed subject -A spawned call may **mutate** state only through a value-typed subject. A `mut` call whose subject is a reference type is a compile-time error at the spawn site. Outside the root package's writes to the program's console and runtime ([`effects.md`](effects.md) §6.6), the subject is the only path by which any call writes state its caller can see, because every other parameter, and every guest derived from one, is read-only ([`effects.md`](effects.md) §4.1, §4.4). The rule is sound because a value type is transitively alias-free — it contains no reference-type or `&` field anywhere downstream (see [`memory.md`](memory.md) §2.10) — so no two names can reach the same mutated object by different paths. A value that owns **boxed members** is no exception: a box holds an instance of the member's own type, and a value copy is deep (see [`memory.md`](memory.md) §2.3), so two values never reach one payload. The compiler therefore rules out an aliased data race from the subject's *type* alone, with no whole-program alias analysis. +A spawned call may **mutate** state only through a value-typed subject. A `mut` call whose subject is a reference type is a compile-time error at the spawn site. Outside the root package's writes to the program's console and runtime ([`effects.md`](effects.md) §6.6), the subject is the only path by which any call writes state its caller can see, because every other parameter, and every reference derived from one, is read-only ([`effects.md`](effects.md) §4.1, §4.4). The rule is sound because a value type is transitively alias-free — it contains no reference-type or `&` field anywhere downstream (see [`memory.md`](memory.md) §2.10) — so no two names can reach the same mutated object by different paths. A value that owns **boxed members** is no exception: a box holds an instance of the member's own type, and a value copy is deep (see [`memory.md`](memory.md) §2.3), so two values never reach one payload. The compiler therefore rules out an aliased data race from the subject's *type* alone, with no whole-program alias analysis. > **Story:** [`stories/concurrency.md`](../stories/concurrency.md#closing-gaps-in-the-two-signature-safety-rules) — "Closing gaps in the two signature-safety rules". @@ -147,9 +147,9 @@ A direct consequence is that spawned work never mutates a reference-typed object ### 4.3 Single writer per storage location -For any one storage location, at most one live spawned call may hold a **mutable borrow** — the `!` subject of a spawned `mut` call. By §4.2 that subject is always value-typed, so every borrow this rule counts is a value borrow ([`memory.md`](memory.md) §2.9). Two spawned calls that mutably borrow the same location are a compile-time error. Because value types carry no `&`, a location's identity is unambiguous — there is no hidden alias to obscure that two subjects denote the same slot — so this disjointness is checked at the spawn site by inspecting the subjects, not by tracing the program. The hosting scope may not access a location while a live spawn holds its mutable borrow; the borrow is released when that spawn completes (§4.1). +For any one storage location, at most one live spawned call may hold a **mutable borrow** — the `!` subject of a spawned `mut` call. By §4.2 that subject is always value-typed, so every borrow this rule counts is a value borrow ([`memory.md`](memory.md) §2.9). Two spawned calls that mutably borrow the same location are a compile-time error. Because value types carry no `&`, a location's identity is unambiguous — there is no hidden alias to obscure that two subjects denote the same slot — so this disjointness is checked at the spawn site by inspecting the subjects, not by tracing the program. The spawning scope may not access a location while a live spawn holds its mutable borrow; the borrow is released when that spawn completes (§4.1). -One spawn site can hold more than one live borrow. A site inside a loop body launches a call per iteration, and §4.1 keeps every one of them live until the scope drains, so inspecting the site's subject once settles nothing. A spawned `mut` call inside a loop body **MUST** take its subject from storage declared inside that body, which gives each iteration its own location; a subject owned by an enclosing scope is a compile-time error. +One spawn site can hold more than one live borrow. A site inside a loop body launches a call per iteration, and §4.1 keeps every one of them live until the scope drains, so inspecting the site's subject once settles nothing. A spawned `mut` call inside a loop body **MUST** take its subject from storage declared inside that body, which gives each iteration its own location; a subject declared in an enclosing scope is a compile-time error. > **Story:** [`stories/concurrency.md`](../stories/concurrency.md#closing-gaps-in-the-two-signature-safety-rules) — "Closing gaps in the two signature-safety rules". @@ -162,7 +162,7 @@ A value whose members are all inline is one fixed-size contiguous slot, so the s - **A stale read is garbage, not an invalid access.** A scope cannot drain while a spawn inside it is live (§4.1), and the runtime unmaps a scope's chunks only at drain (see [`memory.md`](memory.md) §3.2). A block freed during the walk is therefore recycled *within a mapping that stays live*, so a stale handle resolves into readable memory holding some other occupant's bytes. This is the same failure class as a torn flat read, and the version check discards it the same way. - **Structure-directing metadata is untrusted until validation succeeds.** Before the final version check, every byte the walk reads may be torn or may belong to a recycled occupant. The walk **MUST** validate any metadata before using it to choose a typed layout or traversal shape. In particular, a `variant` or `#variant` discriminant must name one of the type's declared cases before case dispatch; an invalid discriminant aborts the attempt and causes a retry. The same rule applies to any count, length, or other metadata used to decide which child handles exist or how many entries to visit. A valid but stale value may still describe the wrong case for the attempted snapshot, but the remaining checks keep that attempt safe and the final version check discards it. - **The walk MUST be bounded, and MUST validate a whole payload span.** Because a recycled block may hold a handle left by its next occupant, a reader may pick up an offset that is not part of the structure it is traversing. Before interpreting what a handle names, the walk **MUST** check that the handle's **complete payload span** — its base offset, plus the size of the member's declared type, at that type's alignment — lies within a live region of the scope, and it **MUST** stop at a **depth bound**. That bound is derived, not arbitrary: a correct walk descends through a distinct live block at every step, so the number of live blocks in the scope's dynamic region when the attempt begins is a depth no legal structure can reach. Fixing the bound there is what keeps exhaustion **retryable** rather than terminal — exceeding it always means the walk is following recycled bytes, and never that the value is legitimately too deep, so the attempt is discarded and retried like any other failed validation. Each attempt takes the bound afresh, since the region may have grown. An offset that merely lands in a live region is not enough: a recycled block can hold one near a region's end, and reading a payload's worth of bytes from there would run past it. A handle failing either check aborts the attempt rather than being followed. -- **The reader allocates in the destination scope.** A deep snapshot is an ordinary deep value copy into its fresh destination binding (see [`memory.md`](memory.md) §2.3). Each boxed payload is therefore allocated from the size stacks of the scope that owns that binding, not generally from the writer's or source value's scope. Snapshotting introduces no special source-scope staging and does not by itself make the reader and writer contend on one stack. Allocator synchronization is required only when concurrent work actually shares an underlying arena. Every block allocated by a snapshot attempt remains **provisional** until the final version check accepts that attempt. If metadata validation, span or depth validation, or the final version check rejects the attempt, the runtime **MUST** return every block allocated by that attempt to the destination scope's corresponding size stacks before retrying. The destination binding becomes live only after the attempt is accepted. +- **The reader allocates in the destination scope.** A deep snapshot is an ordinary deep value copy into its fresh destination binding (see [`memory.md`](memory.md) §2.3). Each boxed payload is therefore allocated from the size stacks of that binding's scope, not generally from the writer's or source value's scope. Snapshotting introduces no special source-scope staging and does not by itself make the reader and writer contend on one stack. Allocator synchronization is required only when concurrent work actually shares an underlying arena. Every block allocated by a snapshot attempt remains **provisional** until the final version check accepts that attempt. If metadata validation, span or depth validation, or the final version check rejects the attempt, the runtime **MUST** return every block allocated by that attempt to the destination scope's corresponding size stacks before retrying. The destination binding becomes live only after the attempt is accepted. Two costs follow and are accepted. A snapshot of such a value allocates and is O(structure) where a flat snapshot allocates nothing, and a retry redoes the whole walk, so a fast writer can starve a reader in a way it cannot for a flat value. A value type that is read under `spawn` on a hot path is therefore better kept flat. @@ -180,9 +180,9 @@ The effect system classifies resource access as **read** or **write**. Concurren The compiler enforces this from effect signatures; the programmer does not add locks. -### 4.6 Guests passed to spawned work remain independent +### 4.6 References passed to spawned work remain independent -When a guest is passed to a spawned call, the callee receives its own guest to the same host. Rebinding the caller's `&` symbol later changes only the caller's storage; it does not retarget the guest already held by spawned work. +When a reference is passed to a spawned call, the callee receives its own reference to the same owner. Rebinding the caller's `&` symbol later changes only the caller's storage; it does not retarget the reference already held by spawned work. > **Story:** [`stories/concurrency.md`](../stories/concurrency.md#safety-the-compiler-proves-from-signatures-not-locks) — "Safety the compiler proves from signatures, not locks". @@ -214,7 +214,7 @@ Zane does not define `async` or `await`. Concurrency is expressed only through ` ### 5.4 No language-level process or channel abstraction -Zane does not define a dedicated `Process` type, actor primitive, or channel primitive in the core language. Long-running concurrent work is expressed as ordinary spawned function or method calls plus explicit state flow governed by hosting and effect rules. +Zane does not define a dedicated `Process` type, actor primitive, or channel primitive in the core language. Long-running concurrent work is expressed as ordinary spawned function or method calls plus explicit state flow governed by ownership and effect rules. > **Story:** [`stories/concurrency.md`](../stories/concurrency.md#what-the-core-deliberately-leaves-out) — "What the core deliberately leaves out". diff --git a/spec/control-flow.md b/spec/control-flow.md index 77dac58..cfc5d6f 100644 --- a/spec/control-flow.md +++ b/spec/control-flow.md @@ -51,7 +51,7 @@ A verb that declares a `@concepts$Block` parameter **MUST NOT** be spawned ([`co ### 2.3 A block is a scope for bindings, not for control transfer -A block owns the symbols declared inside it, and they are destroyed when it ends, like any other lexical block ([`lifetimes.md`](lifetimes.md) §2.1). +A block is the scope of the symbols declared inside it, and they are destroyed when it ends, like any other lexical block ([`lifetimes.md`](lifetimes.md) §2.1). It is **transparent** to control transfer. `return` and `abort` written inside a block act on the invocation containing the *call*, not on the block, and a call to an exiting verb such as `core`'s `guard` (§3.6) ends that same invocation: @@ -318,7 +318,7 @@ This document specifies the ordinal base only. The language-level behavior for o | Block argument | A braced run of statements passed to a call; type `@concepts$Block` or `Block`; no parameters, no name, never a value | | Capture | A block reads and writes its enclosing scope's bindings | | Escape | A block may not be stored, returned, bound, placed in storage, or spawned; it may be handed to another verb | -| Scope | A block owns its own declarations but is transparent to `return` and `abort`, and a `guard` written in one exits the verb the block was written in | +| Scope | A block is the scope of its own declarations but is transparent to `return` and `abort`, and a `guard` written in one exits the verb the block was written in | | Lowering | A verb taking a block parameter is expanded at its call site, transitively, so a block never crosses a call boundary and an exit inside one is a jump within one frame | | Yielding | A `Block` ends its yielding paths with `resolve`; `return` still leaves the enclosing verb | | Branching | `if` returns whether it ran; `ran!elif(...)` continues the chain and writes it; `ran:else()` ends it — all `core` declarations | diff --git a/spec/effects.md b/spec/effects.md index b36f661..4930944 100644 --- a/spec/effects.md +++ b/spec/effects.md @@ -11,8 +11,8 @@ This document specifies Zane's effect model: `mut`, read-only bindings, what eac Zane uses a structural effect model with a single user-facing effect modifier: `mut`. - **`No purity keywords`.** Users do not write `pure`, `readonly`, or capability qualifiers. -- **`Subject-local mutation`.** `mut` grants write access to state reachable through `this`, including through guests. -- **`Read-only everywhere else`.** Every other parameter is read-only, and so is every guest derived from one. A `!` call is a write, exactly as an assignment is. +- **`Subject-local mutation`.** `mut` grants write access to state reachable through `this`, including through references. +- **`Read-only everywhere else`.** Every other parameter is read-only, and so is every reference derived from one. A `!` call is a write, exactly as an assignment is. - **`Three kinds of verb`.** A `mut` method writes `this`. A method without `mut` and a function write nothing their caller can see. The signature says which. - **`Capability-based external effects`.** I/O and external state remain explicit because capability objects must be passed or stored. They originate in `@program$`, which only the root package reaches. @@ -35,7 +35,7 @@ A capability is an object whose methods model access to external state, such as ### 2.3 `mut` -`mut` is the only effect modifier in the language. It appears on methods and grants write access to state reachable through `this`; the write lands on the caller's object or on state reachable from it. `this` is written bare for both kinds and carries no marker: it is a **borrow** of the caller's value or host (see [`functions.md`](functions.md) §2.4). It never takes hosting, so a `mut` call leaves the caller exactly as it found it. +`mut` is the only effect modifier in the language. It appears on methods and grants write access to state reachable through `this`; the write lands on the caller's object or on state reachable from it. `this` is written bare for both kinds and carries no marker: it is a **borrow** of the caller's value or owner (see [`functions.md`](functions.md) §2.4). It never takes ownership: a `mut` call may change the caller's object, but the caller still holds it afterwards. ### 2.4 Parameters are read-only @@ -55,7 +55,7 @@ A verb's signature says what it may write. There are three kinds: | Method without `mut` | `:` | nothing its caller can see | | Function | a plain call | nothing its caller can see | -Every verb may also write storage it hosts itself, which its caller never sees. Whatever a verb calls stays within the same bound: a callee writes only through its own `this`, and the caller supplies that `this` with a `!` call on something the caller may itself write (§4). So a verb's kind bounds everything the call can write, however deep the calls go. +Every verb may also write storage it owns itself, which its caller never sees. Whatever a verb calls stays within the same bound: a callee writes only through its own `this`, and the caller supplies that `this` with a `!` call on something the caller may itself write (§4). So a verb's kind bounds everything the call can write, however deep the calls go. The root package is the one exception. Any verb there may write the program's console and runtime (§6.6). @@ -69,7 +69,7 @@ Any verb may read capability-backed state through a `:` call on a capability it ### 4.1 A read-only binding admits no write -A verb writes a place in one of two ways: it assigns to the place, or it calls a `mut` method with the place as the subject, and that method writes it. A read-only binding admits neither. The read-only bindings are every parameter other than `this`, and `this` in a method without `mut`. Everything reached through a read-only binding is read-only too: its fields, its elements, and the object each of its guests names. +A verb writes a place in one of two ways: it assigns to the place, or it calls a `mut` method with the place as the subject, and that method writes it. A read-only binding admits neither. The read-only bindings are every parameter other than `this`, and `this` in a method without `mut`. Everything reached through a read-only binding is read-only too: its fields, its elements, and the object each of its references names. ```zane Unit report(console &Console, msg String) { @@ -91,23 +91,23 @@ console!log("hello"); ### 4.3 `&` use sites follow ordinary call rules -Reading through a guest is not a side effect by itself. At use sites, guests follow the same field-access and method-call rules as hosts. Mutation of the hosted object's state must still be expressed through a `mut` method call with that object as the subject. +Reading through a reference is not a side effect by itself. At use sites, references follow the same field-access and method-call rules as owners. Mutation of the owned object's state must still be expressed through a `mut` method call with that object as the subject. -### 4.4 Read-only follows the guest +### 4.4 Read-only follows the reference -A guest derived from a read-only binding is read-only wherever it goes: bound to a local, stored in a field, passed as an argument, or returned. The compiler assumes a `mut` method may write through every guest its subject reaches. A `!` call is therefore a compile-time error when its subject reaches a read-only guest through any chain of fields and guests. +A reference derived from a read-only binding is read-only wherever it goes: bound to a local, stored in a field, passed as an argument, or returned. The compiler assumes a `mut` method may write through every reference its subject reaches. A `!` call is therefore a compile-time error when its subject reaches a read-only reference through any chain of fields and references. ```zane Unit f(console &Console) { k &Console = console; k!print("hi"); // ILLEGAL: k is derived from read-only console app App(console); // App stores its argument in an `&` field - app!run(); // ILLEGAL: app reaches a read-only guest + app!run(); // ILLEGAL: app reaches a read-only reference return Unit(); } ``` -Each verb judges this against its own bindings. Inside a verb, its parameters are read-only. At a call site, a guest the verb stores or returns takes the writability of the argument it came from, the same substitution that [`lifetimes.md`](lifetimes.md) §1.11 makes for owners. So `car!setEngine(engine)` leaves `car` writable when `engine` is the caller's own local, and makes `car` reach a read-only guest when `engine` is a parameter of the caller. +Each verb judges this against its own bindings. Inside a verb, its parameters are read-only. At a call site, a reference the verb stores or returns takes the writability of the argument it came from, the same substitution that [`lifetimes.md`](lifetimes.md) §1.11 makes for scopes. So `car!setEngine(engine)` leaves `car` writable when `engine` is the caller's own local, and makes `car` reach a read-only reference when `engine` is a parameter of the caller. > **Story:** [`stories/effects.md`](../stories/effects.md#a-mutating-call-is-a-write) — "A mutating call is a write". @@ -128,9 +128,9 @@ Two facts about a call are not in the signature. The compiler derives them from The compiler uses these facts only to decide what it may evaluate at compile time or run in parallel ([`concurrency.md`](concurrency.md) §2). -### 5.3 Reading through a guest is a read +### 5.3 Reading through a reference is a read -Reading through an `&` is a read like any other. What a verb may write follows from its kind (§3), not from whether it reaches an object through a host or a guest. +Reading through an `&` is a read like any other. What a verb may write follows from its kind (§3), not from whether it reaches an object through an owner or a reference. ### 5.4 Unknown callees are assumed to touch capabilities and not terminate @@ -148,7 +148,7 @@ There is no ambient global I/O capability. Code can reach external state only th ### 6.2 Constructor injection is ordinary capability wiring -Capabilities may be stored into objects at construction time. This does not create ambient authority; it only records an explicit hosting path by which later methods can reach the capability. A capability stored from a writable source can be written by the object's `mut` methods; one stored from a read-only source stays read-only (§4.4). +Capabilities may be stored into objects at construction time. This does not create ambient authority; it only records an explicit owning path by which later methods can reach the capability. A capability stored from a writable source can be written by the object's `mut` methods; one stored from a read-only source stays read-only (§4.4). ### 6.3 `&` fields can also expose read access paths @@ -156,7 +156,7 @@ Storing an `&` field is another explicit way to make state reachable. This does ### 6.4 Context objects are explicit, not magical -A "context object" that groups several capabilities is just another ordinary object in the hosting graph. It may reduce parameter count, but it does not hide effects from the compiler because the reachable capabilities are still explicit in storage and call structure. +A "context object" that groups several capabilities is just another ordinary object in the ownership graph. It may reduce parameter count, but it does not hide effects from the compiler because the reachable capabilities are still explicit in storage and call structure. ### 6.5 Prop drilling is intentional @@ -230,7 +230,7 @@ Concurrent mutation is not a per-`mut`-call property; it is governed by the spaw | `mut` method | Called with `!`; writes `this` and everything reached through it | | Method without `mut`, function | Write nothing their caller can see | | Read-only binding | Every parameter other than a `mut` method's `this`; admits neither an assignment nor a `!` call | -| Derived guest | A guest derived from a read-only binding stays read-only wherever it goes; a `!` call whose subject reaches one is an error | +| Derived reference | A reference derived from a read-only binding stays read-only wherever it goes; a `!` call whose subject reaches one is an error | | Root package | Any verb may write the program's console and runtime | | Derived facts | Capability access and termination come from the body and its callees, and govern only compile-time evaluation and parallelism | | Capabilities | Reachable only as objects passed or stored; they originate in `@program$` | diff --git a/spec/foundations.md b/spec/foundations.md index 3748d17..d540273 100644 --- a/spec/foundations.md +++ b/spec/foundations.md @@ -73,9 +73,9 @@ It is the foundation under a large part of the runtime model: ## 6. Strictness Is the Performance Model -Zane is strict — single hosting, fixed layout, read-only parameters, mandatory error handling — and the strictness is not a separate concern from its performance. It *is* the performance story. +Zane is strict — single ownership, fixed layout, read-only parameters, mandatory error handling — and the strictness is not a separate concern from its performance. It *is* the performance story. -High-level expression, on its own, usually costs speed. What buys it back is that the rules preserve enough invariants for the compiler to generate good code without guessing: hosting is known, so destruction is deterministic and needs no collector; layout is fixed, so access is direct; effects are known, so independent work can be parallelized. Each rule that forbids a convenience is the same rule that licenses an optimization. +High-level expression, on its own, usually costs speed. What buys it back is that the rules preserve enough invariants for the compiler to generate good code without guessing: ownership is known, so destruction is deterministic and needs no collector; layout is fixed, so access is direct; effects are known, so independent work can be parallelized. Each rule that forbids a convenience is the same rule that licenses an optimization. So the rules should not be read as a usability tax levied next to the performance. They are the bargain itself: *give up the conveniences that would force the compiler to be conservative, and in exchange the compiler can be aggressive.* This is why the language forbids, rather than merely discourages, the constructs that would dissolve a guarantee — a guarantee that holds only sometimes is one the compiler cannot rely on. The enforcement mechanisms live in [`memory.md`](memory.md), [`effects.md`](effects.md), and [`lifetimes.md`](lifetimes.md); this section is only the principle that unifies them. @@ -91,13 +91,13 @@ A value type is copied on assignment, has no identity, and — the load-bearing What the axis does **not** decide is **recursion**. Either kind may contain itself, through a member the compiler boxes. That rule and the reasoning behind it belong to [`adt.md`](adt.md) §4. -Both kinds are mutated in place through a `mut` method, and the subject is written the same way in each — bare `this`, no marker — and it is the same thing in each: a *borrow* of the caller's value or host, so a value is mutable without gaining identity and an object is mutable without the method taking hosting. Neither consumes the caller's host. +Both kinds are mutated in place through a `mut` method, and the subject is written the same way in each — bare `this`, no marker — and it is the same thing in each: a *borrow* of the caller's value or owner, so a value is mutable without gaining identity and an object is mutable without the method taking ownership. Neither consumes the caller's owner. - **`#` is the only kind modifier**, applied uniformly to any type. See [`types.md`](types.md) §2 and [`adt.md`](adt.md) §2–§3. - **A value type is transitively value** (no reference-type or `&` field, anywhere downstream). This closed value world is specified by [`memory.md`](memory.md) §2.10. - **A value copy is deep.** Copying a value copies every payload it owns out of line into fresh storage, which is what lets a value type recurse without ever aliasing. See [`memory.md`](memory.md) §2.3. -- **`&` rides on `#`.** A non-hosting `&` exists only for reference types; a value is shared by copy or by a scoped borrow, never by a stored `&`. See [`memory.md`](memory.md) §2.4. -- **A guest names a host that never moves.** A reference-type host is either settled — guestable, never moving again — or roaming — movable, and guested by nothing. A new `&` is minted only from a settled place, so the object it names stays where it is until its scope drains. See [`memory.md`](memory.md) §2.1, §2.8, and §2.8.1. +- **`&` rides on `#`.** A non-owning `&` exists only for reference types; a value is shared by copy or by a scoped borrow, never by a stored `&`. See [`memory.md`](memory.md) §2.4. +- **A reference names an owner that never moves.** An owner is either settled — referenceable, never moving again — or roaming — movable, and referenced by nothing. A new `&` is minted only from a settled place, so the object it names stays where it is until its scope drains. See [`memory.md`](memory.md) §2.1, §2.8, and §2.8.1. - **Concurrency reads this axis.** A spawned call may mutate only a value-typed subject, because a value's transitive alias-freedom is exactly what lets the compiler rule out a data race from the signature alone. See [`concurrency.md`](concurrency.md) §4. > **Story:** [`stories/foundations.md`](../stories/foundations.md#identity-is-opt-in-one-axis-for-value-and-reference) — "Identity is opt-in: one axis for value and reference". diff --git a/spec/functions.md b/spec/functions.md index 39b9899..5a780f2 100644 --- a/spec/functions.md +++ b/spec/functions.md @@ -2,7 +2,7 @@ This document specifies Zane's function model: functions, methods, subscripts, overload resolution, function values, lambdas, and method name resolution. Data declarations and constructors live in [`types.md`](types.md); the package-scope rules that govern these declarations live in [`packages.md`](packages.md). -> **See also:** [`types.md`](types.md) §3 for constructors. [`memory.md`](memory.md) §2 for hosting and `&` rules. [`effects.md`](effects.md) §2 for `mut`. [`syntax.md`](syntax.md) §3 for declaration grammar. +> **See also:** [`types.md`](types.md) §3 for constructors. [`memory.md`](memory.md) §2 for ownership and `&` rules. [`effects.md`](effects.md) §2 for `mut`. [`syntax.md`](syntax.md) §3 for declaration grammar. --- @@ -60,21 +60,21 @@ A method without `mut` may read `this`, its parameters, and reachable read-only ### 2.4 Mutating methods use `mut` -A method marked `mut` may write to any state reachable through `this`, whether through a hosting field or a guest. +A method marked `mut` may write to any state reachable through `this`, whether through an owning field or a reference. -A write to `this` lands on the caller's object, because `this` is a **borrow** of it for either kind of type (see [`memory.md`](memory.md) §2.9). For a value-type subject the borrow is of the caller's slot — the actual value, not a copy — which makes the value mutable in place while preserving its value semantics. For a reference-type subject it is the caller's host, settled or roaming. Nothing is written on `this` to select a mode, because a subject has no other. The caller stays a full host either way. +A write to `this` lands on the caller's object, because `this` is a **borrow** of it for either kind of type (see [`memory.md`](memory.md) §2.9). For a value-type subject the borrow is of the caller's slot — the actual value, not a copy — which makes the value mutable in place while preserving its value semantics. For a reference-type subject it is the caller's owner, settled or roaming. Nothing is written on `this` to select a mode, because a subject has no other. The caller stays a full owner either way. -The borrow is scoped and non-escaping: `this` may be read and, under `mut`, written, and may be the root of an `&` store into the subject ([`lifetimes.md`](lifetimes.md) §1.11), but it is never moved, stored, or returned as `&T`. A verb that hands out a guest into an object takes that object as an `&T` parameter instead. +The borrow is scoped and non-escaping: `this` may be read and, under `mut`, written, and may be the root of an `&` store into the subject ([`lifetimes.md`](lifetimes.md) §1.11), but it is never moved, stored, or returned as `&T`. A verb that hands out a reference into an object takes that object as an `&T` parameter instead. ```zane -Unit setScale(this Node, scale Float) mut { // reference subject: a borrow of the caller's host +Unit setScale(this Node, scale Float) mut { // reference subject: a borrow of the caller's owner this.scale = scale; return Unit(); } ``` ```zane -&Weapon mainWeapon(player &Player) => player.weapon // a guest is handed out by a function +&Weapon mainWeapon(player &Player) => player.weapon // a reference is handed out by a function &Weapon weapon(this Player) => this.weapon // ILLEGAL: this is a borrow ``` @@ -112,17 +112,17 @@ subject!Pkg$method(arg) → Pkg$method(subject, arg) ### 2.7 Parameters are read-only -Explicit parameters other than `this` are read-only. A read-only binding admits no write, and a `!` call is a write: it runs a `mut` method that writes its subject. So a parameter can be neither assigned nor the subject of a `!` call, and neither can anything reached through it or any guest derived from it ([`effects.md`](effects.md) §4.1, §4.4). How each parameter is passed — the three reference modes, or a value borrow — is covered in [`memory.md`](memory.md) §2.9. +Explicit parameters other than `this` are read-only. A read-only binding admits no write, and a `!` call is a write: it runs a `mut` method that writes its subject. So a parameter can be neither assigned nor the subject of a `!` call, and neither can anything reached through it or any reference derived from it ([`effects.md`](effects.md) §4.1, §4.4). How each parameter is passed — the three reference modes, or a value borrow — is covered in [`memory.md`](memory.md) §2.9. -### 2.8 Borrow, take, and guest method parameters +### 2.8 Borrow, take, and reference method parameters A reference-type method parameter selects one of three passing modes ([`memory.md`](memory.md) §2.9): -- A parameter declared as a plain reference type `T` is a **borrow**: the caller passes any host, settled or roaming, or a temporary, and stays a full host. The callee may read it; it may not store, return, or move it. -- A parameter declared as `^T` **takes** its argument: the caller passes a roaming host, which is spent ([`lifetimes.md`](lifetimes.md) §1.8), or a temporary. The callee owns it and may move it on, store it, or return it. -- A parameter declared as `&T` is a **guest**: the caller either supplies a settled place under [`memory.md`](memory.md) §2.8, which mints a guest, or passes an existing `&T` value. The callee may read it, return it, or store it into an `&` field or element. Where it comes to rest is recorded in the signature ([`lifetimes.md`](lifetimes.md) §1.11), and each call decides whether that store is legal. +- A parameter declared as a plain reference type `T` is a **borrow**: the caller passes any owner, settled or roaming, or a temporary, and stays a full owner. The callee may read it; it may not store, return, or move it. +- A parameter declared as `^T` **takes** its argument: the caller passes a roaming owner, which is spent ([`lifetimes.md`](lifetimes.md) §1.8), or a temporary. The callee owns it and may move it on, store it, or return it. +- A parameter declared as `&T` is a **reference**: the caller either supplies a settled place under [`memory.md`](memory.md) §2.8, which mints a reference, or passes an existing `&T` value. The callee may read it, return it, or store it into an `&` field or element. Where it comes to rest is recorded in the signature ([`lifetimes.md`](lifetimes.md) §1.11), and each call decides whether that store is legal. -Only a guest parameter may be bound into `&` storage: a borrow is never stored, and a taken host is roaming, which nothing guests. A value-type parameter is always a read-only borrow. +Only an `&T` parameter may be bound into `&` storage: a borrow is never stored, and a taken owner is roaming, which nothing references. A value-type parameter is always a read-only borrow. ```zane type Car = #struct { @@ -155,9 +155,9 @@ engine Engine(); garage Garage(); car:calculate(engine); // legal: a borrow of engine -car!setEngine(engine); // legal: engine is settled, and one block owns car and engine -car!setEngine(garage.spare); // legal: a field access is a guest source, and - // garage is owned by the same block +car!setEngine(engine); // legal: engine is settled, and one scope holds car and engine +car!setEngine(garage.spare); // legal: a field access is a reference source, and + // garage is in the same scope car!setEngine(Engine()); // ILLEGAL: a temporary is not a place expression do() { spare Engine(); @@ -165,7 +165,7 @@ do() { } ``` -The last two fail for unrelated reasons. A temporary is refused at the source end, by [`memory.md`](memory.md) §2.8; `spare` is a perfectly good guest source and is refused at the destination end, by the store rule ([`lifetimes.md`](lifetimes.md) §1.1) applied to the paths this call supplied. +The last two fail for unrelated reasons. A temporary is refused at the source end, by [`memory.md`](memory.md) §2.8; `spare` is a perfectly good reference source and is refused at the destination end, by the store rule ([`lifetimes.md`](lifetimes.md) §1.1) applied to the paths this call supplied. ### 2.9 Subscripts are place projections @@ -189,7 +189,7 @@ Int (this CustomList)[index Int] => this._data[index] // ILLEGAL: explicit `list[i]` is a place expression only if `list` is a place expression. `CustomList()[1]` is therefore not a place expression because the base is a temporary. -A subscript expression denotes the place its body projects, so it is a guest source exactly when that place is ([`memory.md`](memory.md) §2.8). Following the body through every subscript and field it uses ends at one intrinsic projection, which decides: an `@primitives$ArrayRef` element takes its root's state and may be guested when that root is settled, an `@primitives$List` element is always roaming, and an `@primitives$Array` element is a value. +A subscript expression denotes the place its body projects, so it is a reference source exactly when that place is ([`memory.md`](memory.md) §2.8). Following the body through every subscript and field it uses ends at one intrinsic projection, which decides: an `@primitives$ArrayRef` element takes its root's state and may be referenced when that root is settled, an `@primitives$List` element is always roaming, and an `@primitives$Array` element is a value. ```zane type Squad = #struct { @@ -468,14 +468,14 @@ All verbs share one parameter system (see [`generics.md`](generics.md) §3), one | Verb | A callable; its kind is selected by markers, and each marker unlocks a capability | | Capability markers | `this` first → method (private access); name is a type → constructor (`init{ }`, implicit return); symbol name → operator; no name → lambda | | Method | Package-scope verb whose first parameter is `this` | -| `mut` method | Called with `!`; may mutate state reachable through `this`, which is a mutable borrow of the caller's value or host | +| `mut` method | Called with `!`; may mutate state reachable through `this`, which is a mutable borrow of the caller's value or owner | | Read-only method | Called with `:`; may read but not write `this` | | Function | Identifier-named package-scope verb without `this`; no private-field privilege | | Block-bodied return | Every returning path uses `return expr`; `Unit` receives no fallthrough or bare-return exception | | `&` method parameter | Caller supplies a settled place or an existing `&T` value; callee may read it, store it into `&` fields, or return it | -| Parameters other than `this` | Read-only: never assigned and never the subject of a `!` call, and neither is any guest derived from one | -| Plain `T` method parameter | A borrow: caller passes any host or a temporary and keeps it; callee may read it, never store, return, or move it | -| `^T` method parameter | Takes: caller supplies a move-source — a roaming host symbol, which is spent, or a temporary, which has no symbol to spend; callee owns it | +| Parameters other than `this` | Read-only: never assigned and never the subject of a `!` call, and neither is any reference derived from one | +| Plain `T` method parameter | A borrow: caller passes any owner or a temporary and keeps it; callee may read it, never store, return, or move it | +| `^T` method parameter | Takes: caller supplies a move-source — a roaming owner symbol, which is spent, or a temporary, which has no symbol to spend; callee owns it | | `this` | Always a borrow, mutable under `mut`; never moved, stored, or returned as `&T`, and nothing is written on it to select a mode | | Subscript | Package-scope place projection written `(this T)[...] => placeExpr`; no explicit return type | | Overload identity | Parameter types only; not names, return type, or `mut`; overloads differing only by the passing mode (`T` / `^T` / `&T`), or by the `mut` of a function-type parameter, at one position are illegal | diff --git a/spec/generics.md b/spec/generics.md index fbed6fa..309e02d 100644 --- a/spec/generics.md +++ b/spec/generics.md @@ -412,7 +412,7 @@ Other fixed-size containers (vectors, matrices) are defined in terms of `Array` ### 8.4 ArrayRef is the fixed-size reference primitive -`@primitives$ArrayRef` is a reference-type storage primitive: `n` contiguous elements of type `T`, laid out as `@primitives$Array` is. `T` may be a value type or a reference type, since a reference type may contain either ([`memory.md`](memory.md) §2.10). `core` declares `ArrayRef` over it as a `#` reference type. Its size is statically known, so it lives inline in the fixed-size region like any other statically sized host ([`memory.md`](memory.md) §3.5). +`@primitives$ArrayRef` is a reference-type storage primitive: `n` contiguous elements of type `T`, laid out as `@primitives$Array` is. `T` may be a value type or a reference type, since a reference type may contain either ([`memory.md`](memory.md) §2.10). `core` declares `ArrayRef` over it as a `#` reference type. Its size is statically known, so it lives inline in the fixed-size region like any other statically sized owner ([`memory.md`](memory.md) §3.5). An `ArrayRef` is built from an array literal, or by `ArrayRef.fill`, which calls a lambda once per position, in order, with that position's 1-based index: @@ -421,7 +421,7 @@ squad ArrayRef([Enemy(Int(1)), Enemy(Int(2)), Enemy(Int(3))]); grid ArrayRef.fill(100, ^Enemy(i Int) => Enemy(i)); ``` -Its elements are fixed storage: they are all present from construction and never come or go, so each element takes its root's state, settled or roaming, as a struct field does ([`memory.md`](memory.md) §2.8.1). An element of a settled `ArrayRef` of a reference type may be guested. An element is overwritten in place and is never moved out, under either kind of root. +Its elements are fixed storage: they are all present from construction and never come or go, so each element takes its root's state, settled or roaming, as a struct field does ([`memory.md`](memory.md) §2.8.1). An element of a settled `ArrayRef` of a reference type may be referenced. An element is overwritten in place and is never moved out, under either kind of root. > **Story:** [`stories/memory.md`](../stories/memory.md#arrayref-a-fixed-reference-container-whose-elements-can-be-guested) — "`ArrayRef`: a fixed reference container whose elements can be guested". @@ -458,6 +458,6 @@ The following are intentionally not specified in this version: | Concept-typed literal | Must be wrapped in its destination type before driving inference | | Wrong-kind type argument | A type that cannot fill the slot its parameter reaches, such as a reference type in a value mould's field, is reported at its origin: the explicit argument, or the value argument an inferred type is read from; the diagnostic names the path from there to the rejecting slot | | `@primitives$Array` | Fixed-size value-type storage primitive: `n` contiguous elements of type `T`; `core` declares `Array` over it | -| `@primitives$ArrayRef` | Fixed-size reference-type storage primitive over any `T`, with `Array`'s layout; its elements take their root's state, may be guested under a settled root, and are never moved out; `core` declares `ArrayRef` over it | +| `@primitives$ArrayRef` | Fixed-size reference-type storage primitive over any `T`, with `Array`'s layout; its elements take their root's state, may be referenced under a settled root, and are never moved out; `core` declares `ArrayRef` over it | | `@primitives$List` | Dynamically sized reference-type storage primitive: elements in the dynamic region behind a fixed-size handle; `core` declares `List` over it | | Size in the type | Required for uniform stride and therefore for cheap indexing, copying, embedding, and calls | diff --git a/spec/glossary.md b/spec/glossary.md index fe2022a..8cf3fe2 100644 --- a/spec/glossary.md +++ b/spec/glossary.md @@ -46,7 +46,7 @@ This file gives short, reusable names to concepts that appear across multiple sp ### 2.5 water-tower lifetimes -- **Meaning:** Scope-hosted objects stay alive until every `spawn`ed call in that scope has completed and the scope drains. +- **Meaning:** Objects owned in a scope stay alive until every `spawn`ed call in that scope has completed and the scope drains. - **Why this name:** The source document explains the rule through a water-tower analogy in which each still-running spawned call acts like a plate holding the water level up. - **Canonical home:** [`concurrency.md`](concurrency.md) §4.1 @@ -74,7 +74,7 @@ This file gives short, reusable names to concepts that appear across multiple sp ### 3.1 place expression -- **Meaning:** A place expression denotes an existing storage location. Guest-source eligibility is separate: not every place may mint a new `&` (§3.36). +- **Meaning:** A place expression denotes an existing storage location. Reference-source eligibility is separate: not every place may mint a new `&` (§3.36). - **Why this name:** The term names the expressions that refer to a storage "place" rather than to a temporary value. - **Canonical home:** [`memory.md`](memory.md) §2.8 @@ -204,21 +204,21 @@ This file gives short, reusable names to concepts that appear across multiple sp - **Why this name:** The unifying trait is the executing statement body — a verb *does* something — which is why a constructor (statements ending in `return init{}`) counts and is indistinguishable from a builder helper apart from its `init{}` sugar, while a place-projecting subscript does not. - **Canonical home:** [`functions.md`](functions.md) §1 -### 3.23 settled host +### 3.23 settled owner -- **Meaning:** A reference-type host that may be guested and never moves again: a bare reference-type symbol, or a field or `ArrayRef` element of a settled root. A roaming host settles by moving into a settled place. Overwriting it writes the replacement at the same address. -- **Why this name:** It continues the host/guest register — a guest can only visit a host that has settled — and says the host has stopped moving for good. +- **Meaning:** An owner that may be referenced and never moves again: a bare reference-type symbol, or a field or `ArrayRef` element of a settled root. A roaming owner settles by moving into a settled place. Overwriting it writes the replacement at the same address. +- **Why this name:** A settler has stopped travelling and taken a fixed home; a reference can name only an owner that has done so, and the word says the owner has stopped moving for good. - **Canonical home:** [`memory.md`](memory.md) §2.1 -### 3.24 roaming host +### 3.24 roaming owner -- **Meaning:** A reference-type host that may move anywhere. Neither it nor anything inside it can be guested. It is a symbol, parameter, or return written `^T`, a field or `ArrayRef` element of a roaming root, or any list element or variant payload. -- **Why this name:** The opposite of *settled* in the same register: a host still travelling, which no guest can visit. *Loose* was set aside because the spec already calls `'*` the loose form of an operator. +- **Meaning:** An owner that may move anywhere. Neither it nor anything inside it can be referenced. It is a symbol, parameter, or return written `^T`, a field or `ArrayRef` element of a roaming root, or any list element or variant payload. +- **Why this name:** The opposite of *settled* in the same register: an owner still travelling, which no reference can name. *Loose* was set aside because the spec already calls `'*` the loose form of an operator. - **Canonical home:** [`memory.md`](memory.md) §2.1 ### 3.25 arena placement -- **Meaning:** Where a hosted object's storage is materialized: the arena of the scope that creates it, split into a **fixed-size** region for statically sized slots and the handles that sit in them, and a **dynamic** region for the payloads those handles name. Placement is an unobservable implementation choice. +- **Meaning:** Where an owned object's storage is materialized: the arena of the scope that creates it, split into a **fixed-size** region for statically sized slots and the handles that sit in them, and a **dynamic** region for the payloads those handles name. Placement is an unobservable implementation choice. - **Why this name:** Placement is a choice among **arenas** — the per-scope regions — rather than between a stack and a heap; the creating scope's arena is the default, a parent arena the fallback on escape. - **Canonical home:** [`memory.md`](memory.md) §3.5 @@ -230,8 +230,8 @@ This file gives short, reusable names to concepts that appear across multiple sp ### 3.27 borrow -- **Meaning:** Non-hosting, non-escaping access to a caller's storage for the duration of a call: a bare parameter of either kind of type, and every subject. It is the only way a value type is passed. A borrow is mutable only as a `mut` subject, and a value is copied only when bound into a fresh slot. -- **Why this name:** The callee is lent the caller's storage for the call and gives it back at return — it does not host it and cannot keep it. Unlike a guest, the borrow itself cannot be stored, returned, or used as a move-source — a restriction on the borrow, not on the value read through it, which a value type may still copy into a fresh slot. +- **Meaning:** Non-owning, non-escaping access to a caller's storage for the duration of a call: a bare parameter of either kind of type, and every subject. It is the only way a value type is passed. A borrow is mutable only as a `mut` subject, and a value is copied only when bound into a fresh slot. +- **Why this name:** The callee is lent the caller's storage for the call and gives it back at return — it does not own it and cannot keep it. Unlike a reference, the borrow itself cannot be stored, returned, or used as a move-source — a restriction on the borrow, not on the value read through it, which a value type may still copy into a fresh slot. - **Canonical home:** [`memory.md`](memory.md) §2.9 ### 3.28 coercion site @@ -248,7 +248,7 @@ This file gives short, reusable names to concepts that appear across multiple sp ### 3.30 value mould / reference mould -- **Meaning:** A mould is written in one of two forms: a **value form**, unmarked, or a **reference form**, carrying a leading `#`. The form decides whether the declared type is copied and transitively value, or identity-bearing, moved, and accessible through guests. It does not decide whether the type may recurse — both forms may, through a boxed member (§3.39). +- **Meaning:** A mould is written in one of two forms: a **value form**, unmarked, or a **reference form**, carrying a leading `#`. The form decides whether the declared type is copied and transitively value, or identity-bearing, moved, and accessible through references. It does not decide whether the type may recurse — both forms may, through a boxed member (§3.39). - **Why this name:** The `#` mark names one axis — value versus reference — that crosses every mould. - **Canonical home:** [`types.md`](types.md) §2.1 @@ -258,39 +258,39 @@ This file gives short, reusable names to concepts that appear across multiple sp - **Why this name:** Product and sum are the standard algebraic names for the two `{ }`-bodied shapes; "peer" names the third from the `enum`'s own defining property — uniform, interchangeable, payloadless members — rather than forcing it into the sum family it degenerately belongs to. - **Canonical home:** [`types.md`](types.md) §2.5 (product, sum); [`adt.md`](adt.md) §2 (peer) -### 3.32 host +### 3.32 owner -- **Meaning:** A source-facing symbol, field, or container slot that stores a reference-type object — or its hosting handle — and governs that object's lifetime. This is the role commonly called an **owner** in other languages. Every reference-type object has exactly one host at a time, settled (§3.23) or roaming (§3.24). Moving a roaming object transfers it to a new host. -- **Why this name:** Zane says **host** because a real-life host provides both accommodation and the duration of a guest's stay; the term emphasizes where an object resides and how long it remains available. +- **Meaning:** A source-facing symbol, field, or container slot that stores a reference-type object — or its owning handle — and governs that object's lifetime. Every reference-type object has exactly one owner at a time, settled (§3.23) or roaming (§3.24). Moving a roaming object transfers it to a new owner. +- **Why this name:** It is the ordinary owner of other languages — one per object, governing its lifetime, handed on by a move — so the plain word needs no metaphor. - **Canonical home:** [`memory.md`](memory.md) §2.1 -### 3.33 guest +### 3.33 reference -- **Meaning:** The source-facing `&T`: access to a settled reference-type host (§3.23) without storing that object or controlling its lifetime. A guest may be repointed, copied when assigned or passed, stored in an `&` field or element, or returned as `&T`, but it cannot outlive its host. It is represented by the host's segmented offset. -- **Why this name:** A guest may use what a host provides without owning it, and the guest's stay cannot outlast the host. +- **Meaning:** The source-facing `&T`: access to a settled owner (§3.23) without storing that object or controlling its lifetime. A reference may be repointed, copied when assigned or passed, stored in an `&` field or element, or returned as `&T`, but it cannot outlive its owner. It is represented by the owner's segmented offset. +- **Why this name:** Readers already know a reference as something that reaches an object it does not own and must not outlive it, which is the rule. A **reference type** is exactly the kind of type a reference may name. - **Canonical home:** [`memory.md`](memory.md) §2.4 ### 3.34 taken parameter -- **Meaning:** A `^T` parameter, which takes a roaming host or a temporary from the caller. Passing a roaming host symbol spends it (§3.44). The parameter is then a roaming host of the body, which moves it on or lets it die when the body drains. -- **Why this name:** The callee *takes* the host, plainly and for good, in contrast to a borrow it gives back and a guest it only visits. +- **Meaning:** A `^T` parameter, which takes a roaming owner or a temporary from the caller. Passing a roaming owner symbol spends it (§3.44). The parameter is then a roaming owner of the body, which moves it on or lets it die when the body drains. +- **Why this name:** The callee *takes* the owner, plainly and for good, in contrast to a borrow it gives back and a reference it only names. - **Canonical home:** [`lifetimes.md`](lifetimes.md) §1.8 ### 3.35 relay / consume -- **Meaning:** The two ways a verb can treat a reference-type host it takes (§3.34), told apart by its return. It **relays** the host when it returns `^T`; the caller may bind that return to host the object again. It **consumes** the host when it returns no host. A verb that declares `T` or `&T` instead borrows or takes a guest, and leaves the caller's host unchanged. -- **Why this name:** "Consume" names taking the value for good; "relay" names passing the hosting role through and handing it back out. +- **Meaning:** The two ways a verb can treat an owner it takes (§3.34), told apart by its return. It **relays** the owner when it returns `^T`; the caller may bind that return to own the object again. It **consumes** the owner when it returns no owner. A verb that declares `T` or `&T` instead borrows or takes a reference, and leaves the caller's owner unchanged. +- **Why this name:** "Consume" names taking the value for good; "relay" names passing ownership through and handing it back out. - **Canonical home:** [`lifetimes.md`](lifetimes.md) §1.8 -### 3.36 guest source +### 3.36 reference source -- **Meaning:** A settled place a new `&` may be minted from: a bare settled symbol, a path from a settled root or an `&T` parameter through struct fields and `ArrayRef` elements only, or an `&T` parameter itself. List elements and variant payloads are excluded. A roaming host, and anything reached from one, never originates a guest. -- **Why this name:** The term names the *source* end — where a guest may come from — separately from where a stored guest may go, which is the store rule's business. +- **Meaning:** A settled place a new `&` may be minted from: a bare settled symbol, a path from a settled root or an `&T` parameter through struct fields and `ArrayRef` elements only, or an `&T` parameter itself. List elements and variant payloads are excluded. A roaming owner, and anything reached from one, never originates a reference. +- **Why this name:** The term names the *source* end — where a reference may come from — separately from where a stored reference may go, which is the store rule's business. - **Canonical home:** [`memory.md`](memory.md) §2.8 ### 3.37 passing mode -- **Meaning:** Which of three ways a reference-type argument reaches a callee, fixed entirely by the parameter's surface form: `T` **borrows** it (§3.27), `^T` **takes** it (§3.34), and `&T` takes a **guest** (§3.33). A value-type parameter is always a borrow, and the subject parameter (§3.38) always is one. +- **Meaning:** Which of three ways a reference-type argument reaches a callee, fixed entirely by the parameter's surface form: `T` **borrows** it (§3.27), `^T` **takes** it (§3.34), and `&T` takes a **reference** (§3.33). A value-type parameter is always a borrow, and the subject parameter (§3.38) always is one. - **Why this name:** "Mode" names a choice about *how* the same argument travels rather than *what* it is — the type is unchanged in each, and only the caller's obligations and resulting state differ. - **Canonical home:** [`memory.md`](memory.md) §2.9 @@ -314,25 +314,25 @@ This file gives short, reusable names to concepts that appear across multiple sp ### 3.41 move-source -- **Meaning:** An expression denoting a roaming value that the expression is entitled to consume, and therefore the only thing that may be moved into a hosting position: a roaming symbol, a field of a roaming root, a `^T` result, or a `#variant` case form. A settled host is never one. +- **Meaning:** An expression denoting a roaming value that the expression is entitled to consume, and therefore the only thing that may be moved into an owning position: a roaming symbol, a field of a roaming root, a `^T` result, or a `#variant` case form. A settled owner is never one. - **Why this name:** It names the *source* end of a move, which is where the restriction lives: the rule is about what an expression is entitled to give up, not about where the value lands. - **Canonical home:** [`lifetimes.md`](lifetimes.md) §1.2 -### 3.42 carried guest +### 3.42 carried reference -- **Meaning:** An `&` reachable from a value's **declared** type by following zero or more **owning** edges — the same graph the boxed-member rule reads (§3.39). A type's own `&` field is the zero-edge case. The walk stops at each `&` rather than continuing through it into what it names, since that object is hosted elsewhere and travels separately. A value's carried guests are what the store rule compares alongside the value's own host. -- **Why this name:** The value *carries* the guest the way luggage carries its contents — the guest travels with it and is not part of what the value is used for, which is exactly why the store that relocates the value is the one that has to look inside. +- **Meaning:** An `&` reachable from a value's **declared** type by following zero or more **owning** edges — the same graph the boxed-member rule reads (§3.39). A type's own `&` field is the zero-edge case. The walk stops at each `&` rather than continuing through it into what it names, since that object is owned elsewhere and travels separately. A value's carried references are what the store rule compares alongside the value's owner. +- **Why this name:** The value *carries* the reference the way luggage carries its contents — the reference travels with it and is not part of what the value is used for, which is exactly why the store that relocates the value is the one that has to look inside. - **Canonical home:** [`lifetimes.md`](lifetimes.md) §1.10 -### 3.43 owner +### 3.43 scope -- **Meaning:** The lifetime a place belongs to, and the only thing the store rule compares. A **symbol** is owned by its declaring block; a **field or element** takes its root symbol's owner rather than having one of its own; a `^T` parameter is owned by the body's top block; `this`, an `&T` parameter, and a constructor's `init{ }` have none in the body at all, each standing instead for a path in the caller's frame. -- **Why this name:** It names what a place's lifetime *is owed to* rather than where the place is written, which is the distinction the rule turns on — a field's own position tells you nothing, its root's owner tells you everything. +- **Meaning:** The block whose lifetime bounds a place, and the only thing the store rule compares. A **symbol**'s scope is its declaring block; a **field or element** takes its root symbol's scope rather than having one of its own; a `^T` parameter's is the body's top block; `this`, an `&T` parameter, and a constructor's `init{ }` have none in the body at all, each standing instead for a path in the caller's frame. +- **Why this name:** A place's scope is a lexical scope — a block — so the ordinary word fits. It names where a place's lifetime is bounded rather than where the place is written, which is the distinction the rule turns on — a field's own position tells you nothing, its root's scope tells you everything. - **Canonical home:** [`lifetimes.md`](lifetimes.md) §1.1 ### 3.44 spent symbol -- **Meaning:** A roaming host symbol after its object has been moved out, whether by a direct move or by passing it to a taken parameter (§3.34). It denotes no object, so any use of it is a compile-time error, until a store **refills** it with a new host. A symbol changes between hosting and spent only in its declaration block, and a parameter, being read-only, is never refilled. +- **Meaning:** A roaming owner symbol after its object has been moved out, whether by a direct move or by passing it to a taken parameter (§3.34). It denotes no object, so any use of it is a compile-time error, until a store **refills** it with a new owner. A symbol changes between owning and spent only in its declaration block, and a parameter, being read-only, is never refilled. - **Why this name:** A spent casing has done its job and is empty, and it can be reloaded; the symbol has handed its object on and holds nothing, but keeps the storage for another. - **Canonical home:** [`lifetimes.md`](lifetimes.md) §1.6 diff --git a/spec/lexical.md b/spec/lexical.md index c5c9232..c4aeef8 100644 --- a/spec/lexical.md +++ b/spec/lexical.md @@ -100,8 +100,8 @@ Certain leading characters are reserved and are not ordinary identifier starts: | Sigil | Meaning | Canonical home | |---|---|---| -| `&` | Guest type (`&Node`) | [`memory.md`](memory.md) §2 | -| `^` | Roaming host type (`^Node`) | [`memory.md`](memory.md) §2.1 | +| `&` | Reference type (`&Node`) | [`memory.md`](memory.md) §2 | +| `^` | Roaming owner type (`^Node`) | [`memory.md`](memory.md) §2.1 | | `@` | Intrinsic namespace (`@primitives$`, `@concepts$`, `@controlflow$`, `@runtime$`, `@program$`) | [`syntax.md`](syntax.md) §2.7 | | `$` | Package-member separator (`packageName$member`) | [`packages.md`](packages.md) §1 | | `'` | Loose form of a binary operator (`'*`, `'+`) | [`operators.md`](operators.md) §3.1 | @@ -295,7 +295,7 @@ Structural escapes are recognized once. Neither a backslash produced by `\\` nor | Type parameter | An uppercase name (`T`) declared `T Type` (in a type's `<>` header or inline in a verb); referenced bare | | Digits | Legal in a name except as the first character; carry no special meaning | | Leading `_` | A field is private to `this` methods for its type; a named package-scope declaration is private to its package | -| Leading `&` | `&Node` is a guest type, legal in storage, parameter, and return positions; it is the only marker a type may carry, and it is never written on `this` | +| Leading `&` | `&Node` is a reference type, legal in storage, parameter, and return positions; it is the only marker a type may carry, and it is never written on `this` | | `<>` disambiguation | A type (uppercase) on the left means a type argument list; a value (lowercase) means comparison | | Entry terminator | `;` terminates every entry of a `{ }` body (`struct`/`variant` members marked or unmarked with `#`, `match` arms, `init{ }` fields, field-constructor entries, enum-map entries); always trailing, inline or multiline; newlines are insignificant there | | Entry separator | `,` separates the entries of a `[ ]`, `( )`, or `< >` list (arrays, `enum`, `match` case groups, function-type parameter lists, call/constructor args, parameter lists, generic args and headers); never trailing | diff --git a/spec/lifetimes.md b/spec/lifetimes.md index 5beb185..121f95c 100644 --- a/spec/lifetimes.md +++ b/spec/lifetimes.md @@ -1,8 +1,8 @@ # Zane Lifetimes -This document specifies Zane's lexical lifetime rules: the owner comparison every store makes, moves, and deterministic destruction. It builds on the host and guest storage forms defined in [`memory.md`](memory.md). +This document specifies Zane's lexical lifetime rules: the scope comparison every store makes, moves, and deterministic destruction. It builds on the owner and reference storage forms defined in [`memory.md`](memory.md). -> **See also:** [`memory.md`](memory.md) §2 for hosting and storage, §4 for guests. [`concurrency.md`](concurrency.md) §4 for water-tower lifetimes. [`effects.md`](effects.md) §2 for `mut`. +> **See also:** [`memory.md`](memory.md) §2 for ownership and storage, §4 for references. [`concurrency.md`](concurrency.md) §4 for water-tower lifetimes. [`effects.md`](effects.md) §2 for `mut`. --- @@ -10,29 +10,29 @@ This document specifies Zane's lexical lifetime rules: the owner comparison ever ### 1.1 A store may not raise a value above what it names -Every place has an **owner**, and an owner is a lifetime: +Every place has a **scope**: the block whose lifetime bounds it. -- a **symbol** — a local binding — is owned by the block that declares it -- a **field or element** reached from its root by **owning** steps is owned by that root symbol's owner, never its own. Every element of a container shares the container's owner, so which element it is does not enter the comparison. -- a **`^T` parameter** is a host of the body, owned by the body's top block ([`memory.md`](memory.md) §2.9). -- `this` and an **`&T` parameter**, and a constructor's `init{ }`, have no owner in the body. Each stands for a path in the caller's frame, so a store through one is settled at the call site (§1.11). A borrow parameter is read-only and is never a destination. +- a **symbol** — a local binding — has the block that declares it as its scope +- a **field or element** reached from its root by **owning** steps takes that root symbol's scope, never one of its own. Every element of a container shares the container's scope, so which element it is does not enter the comparison. +- a **`^T` parameter** is an owner of the body, scoped to the body's top block ([`memory.md`](memory.md) §2.9). +- `this` and an **`&T` parameter**, and a constructor's `init{ }`, have no scope in the body. Each stands for a path in the caller's frame, so a store through one is settled at the call site (§1.11). A borrow parameter is read-only and is never a destination. -A path that steps *through* an `&` leaves the tree its root names. What lies beyond belongs to a different tree whose root the path does not mention, so no owner can be computed for it and it is not a place this rule can govern. Such a path may be **read** freely; it may not be the destination of a store: +A path that steps *through* an `&` leaves the tree its root names. What lies beyond belongs to a different tree whose root the path does not mention, so no scope can be computed for it and it is not a place this rule can govern. Such a path may be **read** freely; it may not be the destination of a store: ```zane main.peer.io = someIO; // ILLEGAL: `peer` is an `&`, so `main` does not name // the tree this would write into ``` -A **store** is legal only when every host the stored value names — directly, or through an `&` it **carries** (§1.10) — has an owner that outlives the destination's owner. An assignment, a move, a return, an abort, and an argument are all stores. There is one comparison in this section, and those are the places it is made. +A **store** is legal only when every owner the stored value names — directly, or through an `&` it **carries** (§1.10) — has a scope that outlives the destination's scope. An assignment, a move, a return, an abort, and an argument are all stores. There is one comparison in this section, and those are the places it is made. -Two clauses complete it. A **block** outlives every block nested within it, and which block owns a symbol is fixed at that symbol's declaration, so nothing later can falsify it. And the hosts **inside** a stored value travel with it, taking the destination's owner. +Two clauses complete it. A **block** outlives every block nested within it, and a symbol's scope is fixed at that symbol's declaration, so nothing later can falsify it. And the owners **inside** a stored value travel with it, taking the destination's scope. -A block is one lifetime, not a sequence of them. Everything it owns dies when it drains (§2.1), with no user code interleaved and no order among them to observe, so two things one block owns can never see each other's death. That is why the comparison is between owners rather than between declaration positions. +A block is one lifetime, not a sequence of them. Everything scoped to it dies when it drains (§2.1), with no user code interleaved and no order among them to observe, so two things in one scope can never see each other's death. That is why the comparison is between scopes rather than between declaration positions. ```zane node Node(); -r &Node = node; // legal: one block owns both +r &Node = node; // legal: one scope holds both outerTree Tree(); do() { @@ -42,38 +42,39 @@ do() { } ``` -When a store must **mint** a new guest, its source must also be a settled guest source ([`memory.md`](memory.md) §2.8). That condition is independent of the owner comparison. A store whose source value is already `&T` copies that existing guest instead and does not reapply the minting restriction. +When a store must **mint** a new reference, its source must also be a settled reference source ([`memory.md`](memory.md) §2.8). That condition is independent of the scope comparison. A store whose source value is already `&T` copies that existing reference instead and does not reapply the minting restriction. -A field is **not** confined to its own tree. It inherits its root symbol's owner, so an object and what its `&` field names may be siblings in one block: +A field is **not** confined to its own tree. It inherits its root symbol's scope, so an object and what its `&` field names may be siblings in one block: ```zane io IO(); -terminal Terminal(io); // legal: one block owns terminal and io +terminal Terminal(io); // legal: one scope holds terminal and io ``` -That costs nothing while both sit there. A settled `terminal` never moves, so the comparison is made once. A roaming value moves, and every store of it runs the comparison again over the guests it carries (§1.10). A store through a path that has **no** owner in this frame is the deferred case: `init{ }` fills an object whose destination the constructor cannot see, so the obligation is published in the signature and discharged by each caller (§1.11). +That costs nothing while both sit there. A settled `terminal` never moves, so the comparison is made once. A roaming value moves, and every store of it runs the comparison again over the references it carries (§1.10). A store through a path that has **no** scope in this frame is the deferred case: `init{ }` fills an object whose destination the constructor cannot see, so the obligation is published in the signature and discharged by each caller (§1.11). -The comparison the compiler makes is between two declaration blocks, after resolving each place to the block that owns it. It does not perform borrow inference or lifetime annotation solving. +The comparison the compiler makes is between two declaration blocks, after resolving each place to its scope. It does not perform borrow inference or lifetime annotation solving. > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#inheriting-a-debt-safety-without-a-borrow-checker) — "Inheriting a debt: safety without a borrow checker". > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#where-a-guest-may-be-rooted) — "Where a guest may be rooted". > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#the-owner-lifetime-replaces-the-same-root-rule) — "The owner lifetime replaces the same-root rule". > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#place-lifetimes-inside-a-live-owner) — "Place lifetimes inside a live owner". +> **Story:** [`stories/memory.md`](../stories/memory.md#owner-and-reference-replace-host-and-guest-and-the-store-rule-compares-scopes) — "Owner and reference replace host and guest, and the store rule compares scopes". -### 1.2 Move-sources are roaming hosts, `^T` results, and `#variant` case forms +### 1.2 Move-sources are roaming owners, `^T` results, and `#variant` case forms A move-source must denote a **roaming value the expression is entitled to consume**. Four forms qualify: -- a **roaming host symbol**: a local declared `^T`, or a `^T` parameter, named directly by an identifier expression +- a **roaming owner symbol**: a local declared `^T`, or a `^T` parameter, named directly by an identifier expression - a **field of a roaming root**, reached from such a symbol by field steps ([`memory.md`](memory.md) §2.8.1) -- a **verb result** of type `^T`: a value returned by a verb (function, method, operator, constructor, or lambda) that hands back a roaming host. It has no source host; its source scope is the producing expression, which is always nested within or equal to the destination's scope. A constructor's result is one. -- a **`#variant` case form**: `Variant.case(payload)` where `Variant` is a **reference** sum type (see [`adt.md`](adt.md) §3.2). It is built-in syntax rather than a verb, but it produces a fresh value nothing hosts yet, and it is a move-source on the same terms. A *value* `variant` case form is not one, and does not need to be: a value sum is copied rather than hosted ([`memory.md`](memory.md) §2.3). +- a **verb result** of type `^T`: a value returned by a verb (function, method, operator, constructor, or lambda) that hands back a roaming owner. It has no source owner; its source scope is the producing expression, which is always nested within or equal to the destination's scope. A constructor's result is one. +- a **`#variant` case form**: `Variant.case(payload)` where `Variant` is a **reference** sum type (see [`adt.md`](adt.md) §3.2). It is built-in syntax rather than a verb, but it produces a fresh value nothing owns yet, and it is a move-source on the same terms. A *value* `variant` case form is not one, and does not need to be: a value sum is copied rather than owned ([`memory.md`](memory.md) §2.3). -A verb result and a case form produce a fresh value that no symbol, field, or container hosts yet. Moving it transfers hosting of that temporary straight into the destination. This is what lets a recursive structure be written as one nested expression: each boxed hosting member takes the node built for it in place (see [`adt.md`](adt.md) §4). +A verb result and a case form produce a fresh value that no symbol, field, or container owns yet. Moving it transfers ownership of that temporary straight into the destination. This is what lets a recursive structure be written as one nested expression: each boxed owning member takes the node built for it in place (see [`adt.md`](adt.md) §4). The following are **not** move-sources: -- a **settled** host — a bare reference-type symbol, or a field of a settled root ([`memory.md`](memory.md) §2.1) +- a **settled** owner — a bare reference-type symbol, or a field of a settled root ([`memory.md`](memory.md) §2.1) - an `&` value, including a verb that returns `&T` - a borrow parameter or the subject `this` ([`memory.md`](memory.md) §2.9) - a container element access such as `cars[1]`, or a variant case payload @@ -93,7 +94,7 @@ garage Garage(cars[1]); // ILLEGAL: container element is not a move-source ### 1.3 Moves are restricted to the declaration block -A roaming host symbol, or a field of one, may only be used as a move-source in the exact lexical block where that symbol was declared. A `^T` parameter may be used as a move-source at the top level of the function body. +A roaming owner symbol, or a field of one, may only be used as a move-source in the exact lexical block where that symbol was declared. A `^T` parameter may be used as a move-source at the top level of the function body. ```zane engine ^Engine = Engine(); @@ -121,7 +122,7 @@ Unit loadCar(this Boat, car ^Car) mut { } ``` -This restriction prevents conditional moves and flow-dependent host changes. If control flow is needed, compute the destination or the deciding condition first, then perform a single move in the symbol's declaration block. A store that refills a spent symbol is confined to the same block (§1.6). +This restriction prevents conditional moves and flow-dependent owner changes. If control flow is needed, compute the destination or the deciding condition first, then perform a single move in the symbol's declaration block. A store that refills a spent symbol is confined to the same block (§1.6). The restriction applies only to symbol move-sources. A verb result or `#variant` case form (§1.2) is an unnamed temporary with no declaration block, so it is simply consumed at the point where it appears. @@ -129,7 +130,7 @@ The restriction applies only to symbol move-sources. A verb result or `#variant` ### 1.4 A move needs no scope comparison of its own -A moved host is roaming, so nothing guests it, and its own host has nothing to strand by moving. A roaming symbol moves only in its declaration block (§1.3), so the host it moves into is declared there or above. A settled host never moves, so its owner is fixed where it settles. The only comparison a move makes is §1.1's, over the guests the moved value **carries** (§1.10). +A moved object is roaming, so nothing references it, and moving it strands nothing. A roaming symbol moves only in its declaration block (§1.3), so the owner it moves into is declared there or above. A settled owner never moves, so its scope is fixed where it settles. The only comparison a move makes is §1.1's, over the references the moved value **carries** (§1.10). > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#lifetime-rules-after-settled-and-roaming-hosts) — "Lifetime rules after settled and roaming hosts". @@ -137,9 +138,9 @@ A moved host is roaming, so nothing guests it, and its own host has nothing to s A reference-type parameter is one of three modes ([`memory.md`](memory.md) §2.9), and each has a fixed relation to the call: -- A **borrow** (`T`, and every subject) is the caller's host, lent for the call. It is not stored, returned, or moved, so nothing of it outlives the call, and the caller's host is untouched. -- A **take** (`^T`) moves the caller's roaming host into the body. The parameter is then a roaming host owned by the body's top block (§1.1). The body moves it on — into another parameter's object, into the result, into a local — or it dies when the body drains (§2.1). -- A **guest** (`&T`) is the caller's guest, copied. It stands for the caller's path, so a store that reaches it is settled at the call site (§1.11). +- A **borrow** (`T`, and every subject) is the caller's owner, lent for the call. It is not stored, returned, or moved, so nothing of it outlives the call, and the caller's owner is untouched. +- A **take** (`^T`) moves the caller's roaming owner into the body. The parameter is then a roaming owner scoped to the body's top block (§1.1). The body moves it on — into another parameter's object, into the result, into a local — or it dies when the body drains (§2.1). +- A **reference** (`&T`) is the caller's reference, copied. It stands for the caller's path, so a store that reaches it is settled at the call site (§1.11). ```zane Unit enterMatch(player ^Player) { @@ -151,26 +152,26 @@ Unit enterMatch(player ^Player) { `startMatch` takes `player` into the local `island`, and `island` drains at the return, taking `player` with it. A body that means to keep the player alive hands it back through its result (§1.8), or moves it into an object the caller supplied. -For `&` fields specifically, the callee must declare the corresponding parameter as `&T`. A `^T` parameter is roaming and is never a guest source ([`memory.md`](memory.md) §2.8), and a borrow is never stored. +For `&` fields specifically, the callee must declare the corresponding parameter as `&T`. A `^T` parameter is roaming and is never a reference source ([`memory.md`](memory.md) §2.8), and a borrow is never stored. > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#consumed-or-borrowed-the-parameter-that-lives-at-the-call-site) — "Consumed or borrowed: the parameter that lives at the call site". > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#lifetime-rules-after-settled-and-roaming-hosts) — "Lifetime rules after settled and roaming hosts". ### 1.6 A moved symbol is spent until a store refills it -After a roaming host symbol is moved, it is **spent**: it denotes no object. Reading it, calling a method on it, passing it, or moving it again is a compile-time error. A spent symbol keeps its full storage, and a store into it **refills** it: the symbol then hosts the stored object. Nothing guests a roaming host, so a refill is never observed by anything that watched the old object. +After a roaming owner symbol is moved, it is **spent**: it denotes no object. Reading it, calling a method on it, passing it, or moving it again is a compile-time error. A spent symbol keeps its full storage, and a store into it **refills** it: the symbol then owns the stored object. Nothing references a roaming owner, so a refill is never observed by anything that watched the old object. ```zane engine ^Engine = Engine(); car Car(engine); // engine is moved; engine is spent engine:inspect(); // ILLEGAL: engine is spent engine = Engine(); // refills engine with a new object -engine:inspect(); // legal: engine hosts the new object +engine:inspect(); // legal: engine owns the new object ``` -Passing a roaming host to a `^T` parameter is a move, so it spends the caller's symbol too (§1.8). A field moved out of a roaming root leaves that field spent in the same way, and the root is spent as a whole until every spent field is refilled. +Passing a roaming owner to a `^T` parameter is a move, so it spends the caller's symbol too (§1.8). A field moved out of a roaming root leaves that field spent in the same way, and the root is spent as a whole until every spent field is refilled. -A symbol changes between hosting and spent only in the block where it is declared. A move out of it is confined there by §1.3, and a store that refills it is confined there too, so whether a symbol is spent never depends on which path ran. Overwriting a symbol that still hosts leaves it hosting, so that store is not confined. +A symbol changes between owning and spent only in the block where it is declared. A move out of it is confined there by §1.3, and a store that refills it is confined there too, so whether a symbol is spent never depends on which path ran. Overwriting a symbol that still owns an object leaves it owning one, so that store is not confined. ```zane engine ^Engine = Engine(); @@ -180,7 +181,7 @@ if(ready()) { } ``` -A parameter is never refilled. A store into one is a write, and a parameter is read-only ([`effects.md`](effects.md) §2.4). A body that needs a host back after passing a `^T` parameter on moves the parameter into a local first, and refills that (§1.8). +A parameter is never refilled. A store into one is a write, and a parameter is read-only ([`effects.md`](effects.md) §2.4). A body that needs an owner back after passing a `^T` parameter on moves the parameter into a local first, and refills that (§1.8). > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#downgrade-not-poison-why-there-is-no-use-after-move-read) — "Downgrade, not poison: why there is no use-after-move-read". > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#a-moved-host-is-spent-not-a-guest) — "A moved host is spent, not a guest". @@ -189,31 +190,31 @@ A verb result (§1.2) has no symbol to spend. The temporary is consumed by the m ### 1.7 Returned `&` values must be rooted in an `&T` parameter -A return is a store into the call-site scope, so §1.1 governs it, and this is what the comparison comes to for a returned guest: a function may return an `&T` only when the returned guest is rooted in one of the function's **`&T` parameters** — the parameter used bare, or a field access whose base chain reaches it. +A return is a store into the call-site scope, so §1.1 governs it, and this is what the comparison comes to for a returned reference: a function may return an `&T` only when the returned reference is rooted in one of the function's **`&T` parameters** — the parameter used bare, or a field access whose base chain reaches it. ```zane &Weapon weaponOf(player &Player) => player.weapon ``` -An `&T` parameter stands for a path in the caller's frame (§1.5), so it has no owner the body could compare against. The obligation travels out with the signature and the call site discharges it against the argument path (§1.11), which is where the two owners are finally both in view. +An `&T` parameter stands for a path in the caller's frame (§1.5), so it has no scope the body could compare against. The obligation travels out with the signature and the call site discharges it against the argument path (§1.11), which is where the two scopes are finally both in view. -Nothing else is a root. A **local** is excluded by lifetime: a body block does not outlive the call-site scope, so §1.1 rejects the store outright. A **`^T` parameter** is a host of the body (§1.5), excluded the same way. A **borrow**, `this` included, is never returned at all ([`memory.md`](memory.md) §2.9). +Nothing else is a root. A **local** is excluded by lifetime: a body block does not outlive the call-site scope, so §1.1 rejects the store outright. A **`^T` parameter** is an owner of the body (§1.5), excluded the same way. A **borrow**, `this` included, is never returned at all ([`memory.md`](memory.md) §2.9). ```zane &Node bad() { value Node(); - return value; // ILLEGAL: value is hosted by the body scope, which drains at the return + return value; // ILLEGAL: value is declared in the body, which drains at the return } ``` -This rule governs a return that **is** an `&T`. A return that *carries* one — a hosting value with an `&` reachable inside it — is the same store, and §1.1 compares the carried guest's owner on the same reasoning. +This rule governs a return that **is** an `&T`. A return that *carries* one — an owning value with an `&` reachable inside it — is the same store, and §1.1 compares the scope of the carried reference's owner on the same reasoning. -An `abort` is a store into the call-site scope exactly as a `return` is: its value lands in the caller's handler rather than in the caller's result ([`error-handling.md`](error-handling.md) §3). So an aborted `&T` is held to the same roots, and so is a guest carried by an aborted value: +An `abort` is a store into the call-site scope exactly as a `return` is: its value lands in the caller's handler rather than in the caller's result ([`error-handling.md`](error-handling.md) §3). So an aborted `&T` is held to the same roots, and so is a reference carried by an aborted value: ```zane Int?&Node refused() { value Node(); - abort value; // ILLEGAL: value is hosted by the body scope, which drains at the abort + abort value; // ILLEGAL: value is declared in the body, which drains at the abort } Int?&Node passed(node &Node) { @@ -229,9 +230,9 @@ The handler's binder is then what the call's result would have been: it names wh > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#running-the-examples) — "Running the examples". > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#lifetime-rules-after-settled-and-roaming-hosts) — "Lifetime rules after settled and roaming hosts". -### 1.8 Passing a roaming host to a `^T` parameter spends it +### 1.8 Passing a roaming owner to a `^T` parameter spends it -A `^T` parameter takes its argument by moving it. Passing a roaming host symbol to one uses that symbol as a move-source (§1.2), so the caller's symbol is spent (§1.6) — **whatever the callee does with the value**. The parameter's declared type is the whole contract: `^T` means the caller gives the host up; `T` and `&T` ([`memory.md`](memory.md) §2.9) leave the caller a full host. Nothing in the callee's body changes the outcome the signature already states. +A `^T` parameter takes its argument by moving it. Passing a roaming owner symbol to one uses that symbol as a move-source (§1.2), so the caller's symbol is spent (§1.6) — **whatever the callee does with the value**. The parameter's declared type is the whole contract: `^T` means the caller gives the owner up; `T` and `&T` ([`memory.md`](memory.md) §2.9) leave the caller a full owner. Nothing in the callee's body changes the outcome the signature already states. ```zane car ^Car = Car(); @@ -240,13 +241,13 @@ car:inspect(); // ILLEGAL: car is spent truck Truck(car); // ILLEGAL: car is spent ``` -A verb that takes a host in one of three ways, each fixed by its signature: +A verb that takes an owner in one of three ways, each fixed by its signature: -- it **borrows** — declares the parameter `T`; the caller stays a full host, and the callee may read it. -- it **relays** the host — declares `^T` and returns `^T`; the caller's symbol is spent, and binding the return hosts the object again. -- it **consumes** the host — declares `^T` and returns no host; the caller's symbol is spent, and the value stays wherever the verb placed it, or dies with the body. +- it **borrows** — declares the parameter `T`; the caller stays a full owner, and the callee may read it. +- it **relays** the owner — declares `^T` and returns `^T`; the caller's symbol is spent, and the symbol the return is bound to owns the object again. +- it **consumes** the owner — declares `^T` and returns no owner; the caller's symbol is spent, and the value stays wherever the verb placed it, or dies with the body. -A relay that takes a value and hands it back uses the return path. A parameter is read-only and is never refilled (§1.6), so the body moves `player` into a local first. `startMatch` consumes `kept` into `island`, so `kept` is spent; `enterMatch` then refills it from `returnPlayer`'s return, in `kept`'s own declaration block, so `kept` hosts again and `return kept` is an ordinary move: +A relay that takes a value and hands it back uses the return path. A parameter is read-only and is never refilled (§1.6), so the body moves `player` into a local first. `startMatch` consumes `kept` into `island`, so `kept` is spent; `enterMatch` then refills it from `returnPlayer`'s return, in `kept`'s own declaration block, so `kept` is an owner again and `return kept` is an ordinary move: ```zane ^Player enterMatch(player ^Player) { @@ -254,18 +255,18 @@ A relay that takes a value and hands it back uses the return path. A parameter i island Island = makeIsland(); playerId Int = kept.id; island!startMatch(kept); // startMatch consumes kept; kept is now spent - kept = island!returnPlayer(playerId); // refill: kept is a full host again + kept = island!returnPlayer(playerId); // refill: kept is a full owner again return kept; } Unit main() { player ^Player = makePlayer(); - player = enterMatch(player); // bind to regain the host + player = enterMatch(player); // bind to regain the owner return Unit(); } ``` -Because the signature alone decides the caller's state, there is no interprocedural consumption inference. The resting-place summary of §1.11 does not reopen this. It records **where** a parameter's guest comes to rest, which the caller needs in order to compare owners; it never changes **whether** passing a host spends the caller's symbol, which the declared mode fixes on its own. Leaving a parameter entirely unused is a separate, general matter — a release build rejects an unused parameter whatever its mode. +Because the signature alone decides the caller's state, there is no interprocedural consumption inference. The resting-place summary of §1.11 does not reopen this. It records **where** a parameter's reference comes to rest, which the caller needs in order to compare scopes; it never changes **whether** passing an owner spends the caller's symbol, which the declared mode fixes on its own. Leaving a parameter entirely unused is a separate, general matter — a release build rejects an unused parameter whatever its mode. > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#the-signature-is-the-whole-contract-retiring-inferred-consumption) — "The signature is the whole contract: retiring inferred consumption". > **Story:** [`stories/memory.md`](../stories/memory.md#three-ways-to-hand-over-an-object) — "Three ways to hand over an object". @@ -274,22 +275,22 @@ Because the signature alone decides the caller's state, there is no interprocedu ### 1.9 An ignored `^T` result is destroyed -A return value need not be bound. When a call's result is a roaming host and the call stands as a bare statement, nothing hosts the result and nothing guests it, so it is destroyed at the end of the statement, with every block it owns. An ignored value-type result, including `Unit()`, is simply discarded. +A return value need not be bound. When a call's result is a roaming owner and the call stands as a bare statement, nothing owns the result and nothing references it, so it is destroyed at the end of the statement, with every block it holds. An ignored value-type result, including `Unit()`, is simply discarded. ```zane -car2 ^Car = repair(car); // bind: car2 hosts the result, and may move it on +car2 ^Car = repair(car); // bind: car2 owns the result, and may move it on repair(car3); // legal: the result is destroyed here ``` -Binding the return is how the caller keeps the host. A relayed host that is not bound is gone, which the caller can see at the call: a bare statement keeps nothing. +Binding the return is how the caller keeps the owner. A relayed owner that is not bound is gone, which the caller can see at the call: a bare statement keeps nothing. > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#lifetime-rules-after-settled-and-roaming-hosts) — "Lifetime rules after settled and roaming hosts". -### 1.10 A value carries the guests reachable along owning edges +### 1.10 A value carries the references reachable along owning edges -A value **carries a guest** when an `&` is reachable from its type by following **owning** edges (see [`adt.md`](adt.md) §4). The walk finds an `&` member and stops at it: a type's own `&` field is the shortest case, reached after no edges at all, and an `&` nested inside a hosting field or container element is reached by following those edges to it. The walk does not continue *through* an `&` into what it names, because that object is hosted elsewhere. +A value **carries a reference** when an `&` is reachable from its type by following **owning** edges (see [`adt.md`](adt.md) §4). The walk finds an `&` member and stops at it: a type's own `&` field is the shortest case, reached after no edges at all, and an `&` nested inside an owning field or container element is reached by following those edges to it. The walk does not continue *through* an `&` into what it names, because that object is owned elsewhere. -The hosts a value's carried guests name are what §1.1 compares alongside the value's own host. A roaming value's carried guests name settled hosts outside it, since nothing inside a roaming host is guestable ([`memory.md`](memory.md) §2.8.1). Each keeps the owner it has, and every store of the value asks again whether that owner outlives the new destination: +The owners a value's carried references name are what §1.1 compares alongside the value's owner. A roaming value's carried references name settled owners outside it, since nothing inside a roaming owner is referenceable ([`memory.md`](memory.md) §2.8.1). Each keeps the scope it has, and every store of the value asks again whether that scope outlives the new destination: ```zane outerHolder Holder(Engine(Int(1))); @@ -297,36 +298,36 @@ parked ^Car = Car(outerHolder.engine); // Car holds an `&Engine` do() { innerHolder Holder(Engine(Int(2))); arriving ^Car = Car(innerHolder.engine); - parked = arriving; // ILLEGAL: the guest names a host owned by this -} // block, and parked is owned above it + parked = arriving; // ILLEGAL: the reference names an owner scoped to this +} // block, and parked is scoped above it ``` -The walk reads the **declared type**, not the value's current contents. For a `#variant` that means every case, because which case is live is the flow-sensitive fact §1.3 exists to refuse. That decides only whether a value *may* carry a guest. What a carried guest **names** is read from the value's construction, which §1.3 keeps in the same block as any move of it — so a case form that supplies no `&` names no host, nothing is compared, and the store passes. No valid program is rejected for holding a case the walk had to consider. +The walk reads the **declared type**, not the value's current contents. For a `#variant` that means every case, because which case is live is the flow-sensitive fact §1.3 exists to refuse. That decides only whether a value *may* carry a reference. What a carried reference **names** is read from the value's construction, which §1.3 keeps in the same block as any move of it — so a case form that supplies no `&` names no owner, nothing is compared, and the store passes. No valid program is rejected for holding a case the walk had to consider. -A value with **no source host** — a verb result or a `#variant` case form (§1.2) — is asked the same question, against the host it is bound into: +A value with **no source owner** — a verb result or a `#variant` case form (§1.2) — is asked the same question, against the owner it is bound into: ```zane type Expr = #variant { intLit String; - ref &Node; // an `&` payload, so this case form takes a guest source + ref &Node; // an `&` payload, so this case form takes a reference source } result ^Expr = Expr.intLit("0"); do() { innerTree Tree(); - result = Expr.ref(innerTree.root); // ILLEGAL: the case form carries a guest to -} // this block, and result is owned above it + result = Expr.ref(innerTree.root); // ILLEGAL: the case form carries a reference to +} // this block, and result is scoped above it ``` -`innerTree.root` is a field of a settled root, which is a guest source ([`memory.md`](memory.md) §2.8) and so is what an `&` payload asks for. It would **not** do for a hosting payload, which takes a move-source ([`adt.md`](adt.md) §3.2) — the two payload kinds ask for different things, and only the `&` kind produces a carried guest here. +`innerTree.root` is a field of a settled root, which is a reference source ([`memory.md`](memory.md) §2.8) and so is what an `&` payload asks for. It would **not** do for an owning payload, which takes a move-source ([`adt.md`](adt.md) §3.2) — the two payload kinds ask for different things, and only the `&` kind produces a carried reference here. -A settled value may hold guests into its own fields, wired after it settles ([`memory.md`](memory.md) §2.8.1). It never moves, so those guests are compared once, where they are stored. What none of this reaches is a host destroyed while its tree lives on, which §2.1 answers. +A settled value may hold references into its own fields, wired after it settles ([`memory.md`](memory.md) §2.8.1). It never moves, so those references are compared once, where they are stored. What none of this reaches is an owner destroyed while its tree lives on, which §2.1 answers. > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#an--store-compares-root-symbols) — "An `&` store compares root symbols". ### 1.11 A signature records where its parameters come to rest -A guest parameter, and `this`, have no owner in the body (§1.5), so a store that reaches one cannot be settled there. What the body settles instead is **where a value comes to rest**: when a verb stores a guest parameter, or a guest a `^T` parameter carries, into a place reachable from `this`, from another guest parameter, or from the result, the parameter and the path it lands in are part of that verb's signature. Each call substitutes its own argument paths for the parameters and applies §1.1. +An `&T` parameter, and `this`, have no scope in the body (§1.5), so a store that reaches one cannot be settled there. What the body settles instead is **where a value comes to rest**: when a verb stores an `&T` parameter, or a reference a `^T` parameter carries, into a place reachable from `this`, from another `&T` parameter, or from the result, the parameter and the path it lands in are part of that verb's signature. Each call substitutes its own argument paths for the parameters and applies §1.1. ```zane type Terminal = #struct { @@ -334,9 +335,9 @@ type Terminal = #struct { } type Main = #struct { - terminal Terminal; // a hosting field + terminal Terminal; // an owning field peer &Terminal; // an `&` field - io IO; // a hosting field + io IO; // an owning field } Unit setIO(this Terminal, io &IO) mut { @@ -345,12 +346,12 @@ Unit setIO(this Terminal, io &IO) mut { } ``` -Both paths below resolve to `main`'s owner, because a field takes its root symbol's owner (§1.1) and both are reached from `main`: +Both paths below resolve to `main`'s scope, because a field takes its root symbol's scope (§1.1) and both are reached from `main`: ```zane main Main(); main.terminal!setIO(main.io); // → main.terminal.io = main.io - // one block owns both: legal + // one scope holds both: legal do() { ioInner IO(); main.terminal!setIO(ioInner); // → main.terminal.io = ioInner @@ -367,12 +368,12 @@ Terminal(io &IO) => init{io;} // recorded: io comes to rest at the result' main Main(); do() { ioInner IO(); - t ^Terminal = Terminal(ioInner); // → t.io = ioInner; one block owns both: legal - main.terminal = t; // ILLEGAL: t carries a guest owned by this block, -} // and main is owned above it + t ^Terminal = Terminal(ioInner); // → t.io = ioInner; one scope holds both: legal + main.terminal = t; // ILLEGAL: t carries a reference scoped to this block, +} // and main is scoped above it ``` -A `^T` parameter is recorded the same way for the guests it carries, and that is what settles an argument carrying a guest. Neither frame sees the problem alone — the argument's guest names a host in the call-site scope, and inside the callee the parameter lands in another parameter's object: +A `^T` parameter is recorded the same way for the references it carries, and that is what settles an argument carrying a reference. Neither frame sees the problem alone — the argument's reference names an owner in the call-site scope, and inside the callee the parameter lands in another parameter's object: ```zane cars List; @@ -380,11 +381,11 @@ do() { innerHolder Holder(Engine(Int(2))); arriving ^Car = Car(innerHolder.engine); cars!append(arriving); // append records: value comes to rest in this's elements -} // → ILLEGAL: arriving carries a guest owned by this - // block, and cars is owned above it +} // → ILLEGAL: arriving carries a reference scoped to this + // block, and cars is scoped above it ``` -The summary is **transitive**, in the way the effect summaries of [`effects.md`](effects.md) §5.2 are: a verb that hands a parameter to another verb inherits the resting places that call records for it. Without that, a guest could be laundered by passing it one frame further than the check looked. +The summary is **transitive**, in the way the effect summaries of [`effects.md`](effects.md) §5.2 are: a verb that hands a parameter to another verb inherits the resting places that call records for it. Without that, a reference could be laundered by passing it one frame further than the check looked. ```zane Unit relay(this Terminal, io &IO) mut { @@ -393,11 +394,11 @@ Unit relay(this Terminal, io &IO) mut { } ``` -A recorded path begins at a **root** — `this`, a guest parameter, or the result — and continues with the same **owning** steps §1.1 owns a place by: field selections, and "an element of" for a container. No index is recorded, because every element of a container shares its owner. What a path may not do is step *through* an `&` after its root, for the reason §1.1 gives — beyond that point the path has left the tree its root names. +A recorded path begins at a **root** — `this`, an `&T` parameter, or the result — and continues with the same **owning** steps §1.1 resolves a place by: field selections, and "an element of" for a container. No index is recorded, because every element of a container shares its scope. What a path may not do is step *through* an `&` after its root, for the reason §1.1 gives — beyond that point the path has left the tree its root names. ```zane Unit wire(this Main, io &IO) mut { - this.terminal.io = io; // recorded: `terminal` is a hosting field of `this` + this.terminal.io = io; // recorded: `terminal` is an owning field of `this` this.peer.io = io; // ILLEGAL: `peer` is an `&` mid-path (§1.1) return Unit(); } @@ -418,28 +419,28 @@ The summary is derived from the body and published with the signature, so a call A reference-type object is destroyed at one of three points, each known from the program text: -- its host's **scope drains** without the object having moved elsewhere, which ends every host the scope owns; -- its host is **overwritten** ([`memory.md`](memory.md) §2.2); +- its owner's **scope drains** without the object having moved elsewhere, which ends every owner scoped to it; +- its owner is **overwritten** ([`memory.md`](memory.md) §2.2); - its place **disappears**: a list element is removed or a variant changes case, and the operation does not move the occupant out first. -A settled host never moves, so it dies at its own scope's drain or at an overwrite. A roaming host may move first, and dies wherever it last landed. An object in a disappearing place is roaming ([`memory.md`](memory.md) §2.8.1), so nothing guests it and destroying it with the place leaves nothing to dangle. +A settled owner never moves, so it dies at its own scope's drain or at an overwrite. A roaming owner may move first, and dies wherever it last landed. An object in a disappearing place is roaming ([`memory.md`](memory.md) §2.8.1), so nothing references it and destroying it with the place leaves nothing to dangle. -A **value** has death points that are equally static: its slot is overwritten, or the host, container, or scope holding it dies. Whatever storage that value owns out of line — the payload of a boxed member, and every payload beneath it — is returned at that point, recursively (see [`memory.md`](memory.md) §2.3 and §3.2). No tracking is needed to find the moment, because every one of these points is known from the program text. +A **value** has death points that are equally static: its slot is overwritten, or the owner, container, or scope holding it dies. Whatever storage that value owns out of line — the payload of a boxed member, and every payload beneath it — is returned at that point, recursively (see [`memory.md`](memory.md) §2.3 and §3.2). No tracking is needed to find the moment, because every one of these points is known from the program text. > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#place-lifetimes-inside-a-live-owner) — "Place lifetimes inside a live owner". > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#lifetime-rules-after-settled-and-roaming-hosts) — "Lifetime rules after settled and roaming hosts". ### 2.2 Scopes drain before destruction -If a scope launches concurrent work, objects hosted by that scope remain alive until all spawned work in that scope finishes. This is the water-tower rule (see [`concurrency.md`](concurrency.md) §4.1). +If a scope launches concurrent work, objects owned in that scope remain alive until all spawned work in that scope finishes. This is the water-tower rule (see [`concurrency.md`](concurrency.md) §4.1). -### 2.3 Guest storage never extends lifetime +### 2.3 Reference storage never extends lifetime -Guests do not participate in hosting and cannot prolong an object beyond the lifetime fixed by its host. A guest names a settled host, and §1.1 keeps the guest from outliving it. +References do not participate in ownership and cannot prolong an object beyond the lifetime fixed by its owner. A reference names a settled owner, and §1.1 keeps the reference from outliving it. -### 2.4 Null guests are not a user-facing state +### 2.4 Null references are not a user-facing state -An `&` is never optional and is never tested for emptiness; the runtime exposes no “null guest” programming model to the user. A guest is initialized from a settled host and names that host until the guest dies. The host never moves ([`memory.md`](memory.md) §2.1), and §1.1 compares owners at every store, over the value's own guests and over the guests it carries (§1.10), deferring to the call site wherever a parameter stands in for a path it cannot see (§1.11). +An `&` is never optional and is never tested for emptiness; the runtime exposes no “null reference” programming model to the user. A reference is initialized from a settled owner and names that owner until the reference dies. The owner never moves ([`memory.md`](memory.md) §2.1), and §1.1 compares scopes at every store, over the value's own references and over the references it carries (§1.10), deferring to the call site wherever a parameter stands in for a path it cannot see (§1.11). --- @@ -451,7 +452,7 @@ An `&` is never optional and is never tested for emptiness; the runtime exposes |---|---|---|---|---| | Destruction timing | deterministic | non-deterministic | deterministic | manual / RAII | | GC pauses | ❌ | ✅ | ❌ | ❌ | -| Dangling guest risk | ❌ | ❌ | ❌ | ✅ | +| Dangling reference risk | ❌ | ❌ | ❌ | ✅ | | Lifetime annotations | ❌ | ❌ | ✅ | ❌ | --- @@ -460,19 +461,19 @@ An `&` is never optional and is never tested for emptiness; the runtime exposes | Concept | Rule | |---|---| -| Store | Legal only when every host the stored value names — its own, and every host reached through a guest it carries — has an owner that outlives the destination's owner; an assignment, a move, a return, an abort, and an argument are all stores | -| Owner | A symbol is owned by its declaring block; a field or element reached by owning steps by its root symbol's owner; a `^T` parameter by the body's top block; `this`, an `&T` parameter, and a constructor's `init{ }` have none in the body and stand for a path in the caller's frame. A path stepping *through* an `&` has left its root's tree, has no owner, and may be read but never stored into. A block outlives every block nested in it | +| Store | Legal only when every owner the stored value names — its own, and every owner reached through a reference it carries — has a scope that outlives the destination's scope; an assignment, a move, a return, an abort, and an argument are all stores | +| Scope | A symbol's scope is its declaring block; a field or element reached by owning steps takes its root symbol's; a `^T` parameter's is the body's top block; `this`, an `&T` parameter, and a constructor's `init{ }` have none in the body and stand for a path in the caller's frame. A path stepping *through* an `&` has left its root's tree, has no scope, and may be read but never stored into. A block outlives every block nested in it | | `&` return | Returned or aborted `&T` must be rooted in an `&T` parameter; a local, a `^T` parameter, and a borrow, `this` included, are not roots | -| Guest assignment | Copies an existing `&T` value, or mints from a settled guest source ([`memory.md`](memory.md) §2.8): a bare settled symbol, or a path from a settled root or an `&T` parameter through struct fields and `ArrayRef` elements only | -| Move-source | A roaming host symbol (local or `^T` parameter), a field of a roaming root, a `^T` verb result, or a `#variant` case form; not a settled host, an `&`, a borrow, a container element, or a case payload | -| Move declaration-block restriction | A roaming host symbol may only be moved in the exact lexical block where it was declared; `^T` parameters may be moved at the body top level | -| Move destination scope | Needs no comparison of its own: nothing guests a moved host, and a symbol moves only in its declaration block | -| Carried guest | A value carries every `&` reachable from its **declared** type along owning edges — for a `#variant`, across every case — stopping at each `&` rather than continuing through it; the type decides whether to look, the value's construction decides what is named. Each keeps its owner and is compared at every store of the value | -| Resting place | Where a verb stores a guest parameter, or a guest a `^T` parameter carries, is part of its signature: a path rooted at `this`, a guest parameter, or the result, continuing by owning steps only, never stepping through an `&`. Derived from the body, transitive through the calls the body makes, and published with the signature. A call substitutes the supplied path for the root, keeps the recorded steps, and applies the store rule to the result | -| Spent symbol | After a move, a roaming source symbol is spent: any use is a compile-time error until a store refills it, and it changes between hosting and spent only in its declaration block; a parameter is read-only and is never refilled | -| Parameter modes | A borrow (`T`, and `this`) lasts for the call; a take (`^T`) moves the caller's roaming host into the body, which owns it; a guest (`&T`) stands for the caller's path | -| Hosting argument | A verb **borrows** a host (`T`, caller keeps it), **relays** it (`^T` and returns `^T`, caller may bind it to host again), or **consumes** it (`^T`, no host returned); passing to `^T` spends the caller's symbol whatever the body does | +| Reference assignment | Copies an existing `&T` value, or mints from a settled reference source ([`memory.md`](memory.md) §2.8): a bare settled symbol, or a path from a settled root or an `&T` parameter through struct fields and `ArrayRef` elements only | +| Move-source | A roaming owner symbol (local or `^T` parameter), a field of a roaming root, a `^T` verb result, or a `#variant` case form; not a settled owner, an `&`, a borrow, a container element, or a case payload | +| Move declaration-block restriction | A roaming owner symbol may only be moved in the exact lexical block where it was declared; `^T` parameters may be moved at the body top level | +| Move destination scope | Needs no comparison of its own: nothing references a moved owner, and a symbol moves only in its declaration block | +| Carried reference | A value carries every `&` reachable from its **declared** type along owning edges — for a `#variant`, across every case — stopping at each `&` rather than continuing through it; the type decides whether to look, the value's construction decides what is named. Each keeps its scope and is compared at every store of the value | +| Resting place | Where a verb stores an `&T` parameter, or a reference a `^T` parameter carries, is part of its signature: a path rooted at `this`, an `&T` parameter, or the result, continuing by owning steps only, never stepping through an `&`. Derived from the body, transitive through the calls the body makes, and published with the signature. A call substitutes the supplied path for the root, keeps the recorded steps, and applies the store rule to the result | +| Spent symbol | After a move, a roaming source symbol is spent: any use is a compile-time error until a store refills it, and it changes between owning and spent only in its declaration block; a parameter is read-only and is never refilled | +| Parameter modes | A borrow (`T`, and `this`) lasts for the call; a take (`^T`) moves the caller's roaming owner into the body, as an owner of the body; a reference (`&T`) stands for the caller's path | +| Owning argument | A verb **borrows** an owner (`T`, caller keeps it), **relays** it (`^T` and returns `^T`, caller may bind it to own the object again), or **consumes** it (`^T`, no owner returned); passing to `^T` spends the caller's symbol whatever the body does | | Return value | A return need not be bound; an unbound `^T` result is destroyed at the end of its statement, and an ignored value-type result is discarded | -| Destruction | Deterministic: at the host's scope drain, at an overwrite, or when a list element or variant payload disappears without being moved out; a settled host dies only at its drain or an overwrite | +| Destruction | Deterministic: at the owner's scope drain, at an overwrite, or when a list element or variant payload disappears without being moved out; a settled owner dies only at its drain or an overwrite | > **Story:** [`stories/lifetimes.md`](../stories/lifetimes.md#no-rule-to-spare-the-specific-hole-each-restriction-plugs) — "No rule to spare: the specific hole each restriction plugs". diff --git a/spec/memory.md b/spec/memory.md index 63f55fd..98995e7 100644 --- a/spec/memory.md +++ b/spec/memory.md @@ -1,6 +1,6 @@ # Zane Memory Model -This document specifies Zane's memory model: hosting, guests, and arena layout. Lexical lifetime rules, moves, and deterministic destruction are specified in [`lifetimes.md`](lifetimes.md). +This document specifies Zane's memory model: ownership, references, and arena layout. Lexical lifetime rules, moves, and deterministic destruction are specified in [`lifetimes.md`](lifetimes.md). > **See also:** [`lifetimes.md`](lifetimes.md) for scope rules, moves, and destruction. [`types.md`](types.md) §2 for value and reference types. [`effects.md`](effects.md) §2 for `mut`. [`concurrency.md`](concurrency.md) §4 for water-tower lifetimes. [`syntax.md`](syntax.md) §1 and §2 for storage forms. @@ -8,53 +8,54 @@ This document specifies Zane's memory model: hosting, guests, and arena layout. ## 1. Overview -Zane eliminates dangling guests by combining single hosting, a host that never moves once anything may point at it, and lexical lifetime rules. +Zane eliminates dangling references by combining single ownership, an owner that never moves once anything may point at it, and lexical lifetime rules. -- **`Settled and roaming hosts`.** A reference-type host is either **settled** — it may be guested, and it never moves again — or **roaming** — it may move anywhere, and nothing guests it. A roaming host settles by moving into a settled place (§2.1, §2.8.1). -- **`Guests name settled hosts`.** An `&` — a **guest** — is a non-hosting handle to a settled host of a **reference type** (a `#`-marked type, or a reference-type intrinsic such as `@primitives$List`). A value type has no identity to point at, so it is shared by copy or borrow, never by a stored guest (§2.4). +- **`Settled and roaming owners`.** An owner is either **settled** — it may be referenced, and it never moves again — or **roaming** — it may move anywhere, and nothing references it. A roaming owner settles by moving into a settled place (§2.1, §2.8.1). +- **`References name settled owners`.** An `&` — a **reference** — is a non-owning handle to a settled owner of a **reference type** (a `#`-marked type, or a reference-type intrinsic such as `@primitives$List`). A value type has no identity to point at, so it is shared by copy or borrow, never by a stored reference (§2.4). - **`A value copy is deep`.** A value owns whatever it holds out of line, so copying one copies its backing stores and boxed payloads into fresh storage instead of sharing them (§2.3, §2.10). -- **`Three passing modes`.** A bare reference-type parameter, and every subject, is a **borrow**; `^T` takes the host; `&T` takes a guest. A value-type parameter is always a borrow (§2.9). -- **`Lexical lifetime enforcement`.** Every store is checked against declaration scopes alone (see [`lifetimes.md`](lifetimes.md) §1), and objects are destroyed when their hosting scope drains; there is no tracing garbage collector (see [`lifetimes.md`](lifetimes.md) §2). -- **`Regioned arena placement`.** Every scope owns separate fixed-size and dynamic regions. Statically sized storage is placed inline in the fixed-size region; resizable data and the payloads of boxed members use the dynamic region (§3). -- **`A guest is an address`.** A settled host never moves, so a guest stores the host's segmented offset directly (§4). +- **`Three passing modes`.** A bare reference-type parameter, and every subject, is a **borrow**; `^T` takes the owner; `&T` takes a reference. A value-type parameter is always a borrow (§2.9). +- **`Lexical lifetime enforcement`.** Every store is checked against declaration scopes alone (see [`lifetimes.md`](lifetimes.md) §1), and objects are destroyed when their owner's scope drains; there is no tracing garbage collector (see [`lifetimes.md`](lifetimes.md) §2). +- **`Regioned arena placement`.** Every scope has separate fixed-size and dynamic regions. Statically sized storage is placed inline in the fixed-size region; resizable data and the payloads of boxed members use the dynamic region (§3). +- **`A reference is an address`.** A settled owner never moves, so a reference stores the owner's segmented offset directly (§4). -The source language uses two words for the relationship: an object lives in a **host**, and a **guest** (`&T`) may access it without storing it or controlling its lifetime. A guest may point only at a settled host, and a settled host stays where it is until its scope drains, so a guest never needs to follow anything. +The source language uses two words for the relationship: an object is held by an **owner**, and a **reference** (`&T`) may access it without storing it or controlling its lifetime. A reference may point only at a settled owner, and a settled owner stays where it is until its scope drains, so a reference never needs to follow anything. > **Story:** [`stories/memory.md`](../stories/memory.md#safety-without-a-collector-and-without-lifetimes) — "Safety without a collector and without lifetimes". > **Story:** [`stories/memory.md`](../stories/memory.md#settled-and-roaming-the-host-that-stopped-moving) — "Settled and roaming: the host that stopped moving". --- -## 2. Hosting and Storage +## 2. Ownership and Storage -### 2.1 Every reference-type instance has exactly one host, settled or roaming +### 2.1 Every reference-type instance has exactly one owner, settled or roaming -Every instance of a reference type (a `#`-marked type, see [`types.md`](types.md) §2.1) is hosted by exactly one symbol, field, element, or payload at a time. A host is in one of two states: +Every instance of a reference type (a `#`-marked type, see [`types.md`](types.md) §2.1) is owned by exactly one symbol, field, element, or payload at a time. An owner is in one of two states: -- A **settled** host may be guested (§2.8). It never moves: no expression takes its object out of it. -- A **roaming** host may be moved (see [`lifetimes.md`](lifetimes.md) §1.2). Nothing guests it, or anything inside it. +- A **settled** owner may be referenced (§2.8). It never moves: no expression takes its object out of it. +- A **roaming** owner may be moved (see [`lifetimes.md`](lifetimes.md) §1.2). Nothing references it, or anything inside it. -A symbol, parameter, or return type written with `^` is roaming; a bare symbol of a reference type is settled. A field or an `ArrayRef` element takes the state of the root it is reached from, and a list element or variant payload is always roaming (§2.8.1). A roaming host **settles** when it moves into a settled place, and a settled host never becomes roaming. +A symbol, parameter, or return type written with `^` is roaming; a bare symbol of a reference type is settled. A field or an `ArrayRef` element takes the state of the root it is reached from, and a list element or variant payload is always roaming (§2.8.1). A roaming owner **settles** when it moves into a settled place, and a settled owner never becomes roaming. ```zane spare ^Engine = Engine(Int(1)); // roaming engine Engine = spare; // settles here; spare is spent view &Engine = engine; // legal: engine is settled -moved Engine = engine; // ILLEGAL: a settled host never moves +moved Engine = engine; // ILLEGAL: a settled owner never moves ``` > **Story:** [`stories/memory.md`](../stories/memory.md#settled-and-roaming-the-host-that-stopped-moving) — "Settled and roaming: the host that stopped moving". +> **Story:** [`stories/memory.md`](../stories/memory.md#owner-and-reference-replace-host-and-guest-and-the-store-rule-compares-scopes) — "Owner and reference replace host and guest, and the store rule compares scopes". -### 2.2 A settled host is overwritten in place +### 2.2 A settled owner is overwritten in place -Any hosting storage position for a reference-type instance — a symbol, field, element, or payload — **MUST** be directly initialized, and **MAY** later be overwritten. +Any owning storage position for a reference-type instance — a symbol, field, element, or payload — **MUST** be directly initialized, and **MAY** later be overwritten. ```zane tank Tank(...); tank = Tank(...); // legal ``` -Overwriting a settled host destroys the old occupant and writes the replacement at the same address. This holds at every depth a guest can reach: each boxed member reached from the host through struct fields and `ArrayRef` elements (§3.3) is written into the block the occupant's member already holds, recursively, so no such member moves to a new block (§3.6). A variant payload is not followed, since nothing guests into one (§2.8.1). A fresh replacement is constructed directly in that storage; a moved-in replacement is copied into it, and the blocks that held its boxed members are returned. A guest to the host, or to any field of it, inline or boxed, therefore observes the replacement. +Overwriting a settled owner destroys the old occupant and writes the replacement at the same address. This holds at every depth a reference can reach: each boxed member reached from the owner through struct fields and `ArrayRef` elements (§3.3) is written into the block the occupant's member already holds, recursively, so no such member moves to a new block (§3.6). A variant payload is not followed, since nothing references into one (§2.8.1). A fresh replacement is constructed directly in that storage; a moved-in replacement is copied into it, and the blocks that held its boxed members are returned. A reference to the owner, or to any field of it, inline or boxed, therefore observes the replacement. ```zane car Car(...); @@ -62,9 +63,9 @@ r &Engine = car.engine; car.engine = Engine(); // the old engine is destroyed; r observes the replacement ``` -Overwriting a roaming host, a list element, or a variant payload destroys the old occupant unless the operation first moves it elsewhere. Nothing guests it, so nothing observes the change of occupant. +Overwriting a roaming owner, a list element, or a variant payload destroys the old occupant unless the operation first moves it elsewhere. Nothing references it, so nothing observes the change of occupant. -An `&T` stored *as an element value* is different: rewriting that element merely replaces one guest value with another. +An `&T` stored *as an element value* is different: rewriting that element merely replaces one reference value with another. > **Story:** [`stories/memory.md`](../stories/memory.md#settled-overwrites-stay-in-place-and-only-an-escape-relocates) — "Settled overwrites stay in place, and only an escape relocates". @@ -95,18 +96,18 @@ An overwrite evaluates its right-hand side against the destination's **pre-overw Allocation is no more a language-visible failure mode here than it is when a `List` outgrows its backing store (§3.6). -Destruction is the mirror. When a value dies — its host dies, its container dies, its scope drains, or its slot is overwritten (see [`lifetimes.md`](lifetimes.md) §2.1) — every block it owns is returned, recursively (§3.2). +Destruction is the mirror. When a value dies — its owner dies, its container dies, its scope drains, or its slot is overwritten (see [`lifetimes.md`](lifetimes.md) §2.1) — every block it owns is returned, recursively (§3.2). > **Story:** [`stories/memory.md`](../stories/memory.md#what-a-copy-is-for-and-the-ban-that-survived-it) — "What a copy is for, and the ban that survived it". > **Story:** [`stories/memory.md`](../stories/memory.md#the-borrow-comes-back-without-a-sigil) — "The borrow comes back, without a sigil". -### 2.4 `&` is a guest: non-hosting storage +### 2.4 `&` is a reference: non-owning storage -`&` creates a **guest**: non-hosting storage that points at a settled host of a **reference type**. An `&T` requires `T` to be a reference type — a declared `#struct`/`#variant`/`#enum`, or a reference-type intrinsic such as `@primitives$List` — because only a reference type has a host to point at. A value type is shared by copying it or by a borrow (see [`functions.md`](functions.md) §2.4), never by a stored guest. Writing `&Node` names a guest to a reference type; a bare `&Int` over a value type is ill-formed. +`&` creates a **reference**: non-owning storage that points at a settled owner of a **reference type**. An `&T` requires `T` to be a reference type — a declared `#struct`/`#variant`/`#enum`, or a reference-type intrinsic such as `@primitives$List` — because only a reference type has an owner to point at. A value type is shared by copying it or by a borrow (see [`functions.md`](functions.md) §2.4), never by a stored reference. Writing `&Node` names a reference to a reference type; a bare `&Int` over a value type is ill-formed. -An explicitly declared `&T` slot is **guest-only**: it stores only the guest and can never host a `T`. A slot declared as `T` or `^T` is **host-capable**. After a roaming host's value moves out, that same full-size slot is **spent** ([`lifetimes.md`](lifetimes.md) §1.6): it denotes no object until a store refills it. +An explicitly declared `&T` slot is **reference-only**: it stores only the reference and can never own a `T`. A slot declared as `T` or `^T` is an **owning slot**. After a roaming owner's value moves out, that same full-size slot is **spent** ([`lifetimes.md`](lifetimes.md) §1.6): it denotes no object until a store refills it. -A guest may be declared as: +A reference may be declared as: - a local symbol - a reference-type field @@ -119,18 +120,19 @@ An `&` type is legal in storage sites (local symbols, fields, nested storage typ Declaring an `&` symbol is legal; §2.8 governs what may initialize it. > **Story:** [`stories/memory.md`](../stories/memory.md#two-vocabularies-host-and-guest-above-anchor-and-tether) — "Two vocabularies: host and guest above anchor and tether". +> **Story:** [`stories/memory.md`](../stories/memory.md#owner-and-reference-replace-host-and-guest-and-the-store-rule-compares-scopes) — "Owner and reference replace host and guest, and the store rule compares scopes". -### 2.5 Guests are repointable +### 2.5 References are repointable -An `&` symbol or `&` field may be assigned a different guest later, either by copying an existing `&T` value or by minting one from a settled place (§2.8), as long as the store rule in [`lifetimes.md`](lifetimes.md) §1.1 is satisfied. For an `&` **field or element**, the owner that rule compares is the field's root symbol's, not the field's own. +An `&` symbol or `&` field may be assigned a different reference later, either by copying an existing `&T` value or by minting one from a settled place (§2.8), as long as the store rule in [`lifetimes.md`](lifetimes.md) §1.1 is satisfied. For an `&` **field or element**, the scope that rule compares is the field's root symbol's, not the field's own. -### 2.6 Guests are independent +### 2.6 References are independent -Assigning or passing a guest gives the destination its own guest to the same host. Rebinding one guest's storage site later changes only that storage site; it does not retarget other guests that already point to that host. +Assigning or passing a reference gives the destination its own reference to the same owner. Rebinding one reference's storage site later changes only that storage site; it does not retarget other references that already point to that owner. -### 2.7 Guests and hosts use the same surface operations +### 2.7 References and owners use the same surface operations -At use sites, a guest is used with the same surface syntax as a direct host. Method calls, field access, and `mut` calls use the ordinary syntax. The distinction between host and guest matters only at the storage site: a guest stores a non-hosting link, while a host stores the object itself. +At use sites, a reference is used with the same surface syntax as a direct owner. Method calls, field access, and `mut` calls use the ordinary syntax. The distinction between owner and reference matters only at the storage site: a reference stores a non-owning link, while an owner stores the object itself. ### 2.8 Place expressions and new `&` values @@ -138,12 +140,12 @@ A **place expression** is an expression that denotes an existing storage locatio The following are place expressions: -- a named local, field-backed, or hosting/`&` storage symbol such as `engine` +- a named local, field-backed, or owning/`&` storage symbol such as `engine` - a field access whose base is a place, such as `car.engine` or `this.engine` - a subscript expression `list[index]` when `list` is a place expression and `[]` is defined as a place projection for that subject type -- an `&T` guest parameter inside the callee body (§2.9) +- an `&T` parameter inside the callee body (§2.9) -Only a **settled** place may mint a new guest. A new `&` value may be minted from: +Only a **settled** place may mint a new reference. A new `&` value may be minted from: - a **bare settled symbol** — a local or a package constant - a path from a settled root that passes only through struct fields and `ArrayRef` elements, such as `car.engine` or `squad[2].weapon` @@ -151,9 +153,9 @@ Only a **settled** place may mint a new guest. A new `&` value may be minted fro Four things are rejected: -- A roaming host, and any place reached from one, is never a guest source. Nothing guests a host that may still move. -- A subscript is a guest source only when the place it projects is an `ArrayRef` element ([`functions.md`](functions.md) §2.9). A list element is roaming, so `players[100]` and `players[100].weapon` on a `List` are both excluded. -- A variant case payload is never a guest source, and neither is a path that continues through one. +- A roaming owner, and any place reached from one, is never a reference source. Nothing references an owner that may still move. +- A subscript is a reference source only when the place it projects is an `ArrayRef` element ([`functions.md`](functions.md) §2.9). A list element is roaming, so `players[100]` and `players[100].weapon` on a `List` are both excluded. +- A variant case payload is never a reference source, and neither is a path that continues through one. - Temporaries and other value-only expressions are not place expressions at all. Constructor calls and ordinary function results such as `Engine()` and `makeEngine()` are not places. ```zane @@ -180,7 +182,7 @@ squad ArrayRef([Player(), Player()]); lead &Weapon = squad[1].weapon; // legal: squad is settled, and its elements are too ``` -For a list element or a case payload, keep the guest at the settled container and perform the access through it when needed. A guest to a container may subscript that container, and a guest to a variant may read whichever case is live; neither access may mint a new guest to the element or case payload. +For a list element or a case payload, keep the reference at the settled container and perform the access through it when needed. A reference to a container may subscript that container, and a reference to a variant may read whichever case is live; neither access may mint a new reference to the element or case payload. Reading an `&T` value that is already stored behind such an access remains legal: @@ -190,25 +192,25 @@ weapons List<&Weapon> = [armory.primary, armory.backup]; current &Weapon = weapons[1]; // legal: reads an `&Weapon` already stored in the list ``` -The last line copies an existing guest value; it does not mint a new `&` from an element. +The last line copies an existing reference value; it does not mint a new `&` from an element. -Non-`&` host bindings may be initialized from any expression, including temporaries. The host materializes the value into its storage. +Non-`&` owner bindings may be initialized from any expression, including temporaries. The owner materializes the value into its storage. ```zane -engine Engine(); // legal: plain host binding; Engine() temporary is materialized into engine +engine Engine(); // legal: plain owner binding; Engine() temporary is materialized into engine ``` > **Story:** [`stories/memory.md`](../stories/memory.md#contingent-hosts-float-to-their-owner) — "Contingent hosts float to their owner". > **Story:** [`stories/memory.md`](../stories/memory.md#arrayref-a-fixed-reference-container-whose-elements-can-be-guested) — "`ArrayRef`: a fixed reference container whose elements can be guested". -### 2.8.1 A roaming host settles where it lands +### 2.8.1 A roaming owner settles where it lands -A roaming host settles by moving into a settled place: a settled symbol, or a field of a settled root. The move is the last time the object moves. From then on it may be guested, and every guest to it names the same address until its scope drains ([`lifetimes.md`](lifetimes.md) §2.1). +A roaming owner settles by moving into a settled place: a settled symbol, or a field of a settled root. The move is the last time the object moves. From then on it may be referenced, and every reference to it names the same address until its scope drains ([`lifetimes.md`](lifetimes.md) §2.1). ```zane ^Car make(power Int) { car ^Car = Car(Engine(power)); - car.engine!tune(); // a roaming host is used like any other + car.engine!tune(); // a roaming owner is used like any other return car; // and moves } @@ -216,61 +218,61 @@ parked Car = make(Int(1)); // settles here garage.car = make(Int(2)); // settles in garage.car when garage is settled ``` -What a host contains takes a state from where it sits: +What an owner contains takes a state from where it sits: -- **Fixed storage inherits its root's state.** A struct's fields and an `ArrayRef`'s elements are settled under a settled root and roaming under a roaming one: they are fixed in number and all initialized at construction ([`generics.md`](generics.md) §8.4). An `Array` is a value type ([`generics.md`](generics.md) §8.1), so it holds no host at all (§2.10). -- **Dynamic storage is always roaming.** A list's elements and a variant's payload come and go while their owner lives, so they are roaming even under a settled root. A settled list may be guested as a whole; its elements may not. +- **Fixed storage inherits its root's state.** A struct's fields and an `ArrayRef`'s elements are settled under a settled root and roaming under a roaming one: they are fixed in number and all initialized at construction ([`generics.md`](generics.md) §8.4). An `Array` is a value type ([`generics.md`](generics.md) §8.1), so it holds no owner at all (§2.10). +- **Dynamic storage is always roaming.** A list's elements and a variant's payload come and go while the list or variant holding them lives, so they are roaming even under a settled root. A settled list may be referenced as a whole; its elements may not. A field is never declared roaming. An `ArrayRef` element is never moved out, under either kind of root: its index is a runtime value, so which element is spent could not be tracked. A field of a **roaming** root may be moved out, because nothing can observe the root; the root is then partly spent, tracked in its declaration block as a spent symbol is ([`lifetimes.md`](lifetimes.md) §1.6). A field of a settled root is overwritten, never moved out (§2.2). -A roaming value cannot hold a guest into its own insides, because nothing inside a roaming host is guestable. An object is wired to its own parts after it settles, from outside it: +A roaming value cannot hold a reference into its own insides, because nothing inside a roaming owner is referenceable. An object is wired to its own parts after it settles, from outside it: ```zane kit Kit(Engine(Int(1)), Mount()); kit.mount!attach(kit.engine); // legal: kit and its fields are settled ``` -The `&` fields a roaming value holds may name settled hosts elsewhere; every store of the value compares them against its destination ([`lifetimes.md`](lifetimes.md) §1.10). +The `&` fields a roaming value holds may name settled owners elsewhere; every store of the value compares them against its destination ([`lifetimes.md`](lifetimes.md) §1.10). > **Story:** [`stories/memory.md`](../stories/memory.md#settled-and-roaming-the-host-that-stopped-moving) — "Settled and roaming: the host that stopped moving". > **Story:** [`stories/memory.md`](../stories/memory.md#where-a-new-ref-may-come-from) — "Where a new ref may come from". > **Story:** [`stories/memory.md`](../stories/memory.md#arrayref-a-fixed-reference-container-whose-elements-can-be-guested) — "`ArrayRef`: a fixed reference container whose elements can be guested". -### 2.9 Function parameters: borrow, take, and guest +### 2.9 Function parameters: borrow, take, and reference A **reference type** parameter has three passing modes, one per surface form: | Mode | Written | Caller supplies | The callee may | |---|---|---|---| -| Borrow | `T` | any host, settled or roaming, or a temporary | read it; never write it, store it, return it, or move it | -| Take | `^T` | a roaming host, which is spent, or a temporary ([`lifetimes.md`](lifetimes.md) §1.2) | move it, store it, or return it; it dies with the body otherwise | -| Guest | `&T` | a settled place that mints a guest, or an existing `&T` value (§2.8) | read it, return it as `&T`, or store it; where a stored guest comes to rest is part of the signature ([`lifetimes.md`](lifetimes.md) §1.11) | +| Borrow | `T` | any owner, settled or roaming, or a temporary | read it; never write it, store it, return it, or move it | +| Take | `^T` | a roaming owner, which is spent, or a temporary ([`lifetimes.md`](lifetimes.md) §1.2) | move it, store it, or return it; it dies with the body otherwise | +| Reference | `&T` | a settled place that mints a reference, or an existing `&T` value (§2.8) | read it, return it as `&T`, or store it; where a stored reference comes to rest is part of the signature ([`lifetimes.md`](lifetimes.md) §1.11) | -- A **borrow** is non-hosting, non-escaping access to the caller's host for the duration of the call. It has no address the callee could keep: a borrow cannot be stored, returned, or minted into a guest. Like every parameter other than `this`, it is read-only ([`effects.md`](effects.md) §2.4), so it is never assigned or the subject of a `!` call; the subject is the one borrow a `mut` method may write. The caller stays a full host. -- A **take** moves the host into the callee. The parameter is a roaming host of the body; the body moves it on — into another parameter's object, into the result, into a local — or it dies when the body's scope drains. -- A **guest** parameter is an ordinary guest. Inside the callee body the parameter acts as a place expression that may be read or returned as `&T` under [`lifetimes.md`](lifetimes.md) §1.7. Binding it into an `&` **field** is decided at each call: the callee records that the parameter comes to rest in that field ([`lifetimes.md`](lifetimes.md) §1.11), and each call compares the owners of the two argument paths it actually wrote. +- A **borrow** is non-owning, non-escaping access to the caller's owner for the duration of the call. It has no address the callee could keep: a borrow cannot be stored, returned, or minted into a reference. Like every parameter other than `this`, it is read-only ([`effects.md`](effects.md) §2.4), so it is never assigned or the subject of a `!` call; the subject is the one borrow a `mut` method may write. The caller stays a full owner. +- A **take** moves the owner into the callee. The parameter is a roaming owner of the body; the body moves it on — into another parameter's object, into the result, into a local — or it dies when the body's scope drains. +- A **reference** parameter is an ordinary reference. Inside the callee body the parameter acts as a place expression that may be read or returned as `&T` under [`lifetimes.md`](lifetimes.md) §1.7. Binding it into an `&` **field** is decided at each call: the callee records that the parameter comes to rest in that field ([`lifetimes.md`](lifetimes.md) §1.11), and each call compares the scopes of the two argument paths it actually wrote. ```zane Float topSpeed(engine Engine) => engine.speed // borrow engine Engine(); -s Float = topSpeed(engine); // legal: engine stays a full host +s Float = topSpeed(engine); // legal: engine stays a full owner spare ^Engine = Engine(); -t Float = topSpeed(spare); // legal: a borrow takes a roaming host too +t Float = topSpeed(spare); // legal: a borrow takes a roaming owner too ``` -A **value type** parameter has one mode, the borrow: a read-only borrow of the caller's slot for the duration of the call. A borrow is not storage, but that restriction is on the borrow, not on what is read through one. Binding through a borrow into a fresh slot (an assignment, a new declaration, or a field or return store) **copies** the value (§2.3). The copy outlives the call perfectly well; what does not escape is the borrow. Neither `^` nor `&` is written on a value-type parameter. A type parameter written `^T` takes a host when `T` is a reference type, and is a borrow when `T` is a value type. +A **value type** parameter has one mode, the borrow: a read-only borrow of the caller's slot for the duration of the call. A borrow is not storage, but that restriction is on the borrow, not on what is read through one. Binding through a borrow into a fresh slot (an assignment, a new declaration, or a field or return store) **copies** the value (§2.3). The copy outlives the call perfectly well; what does not escape is the borrow. Neither `^` nor `&` is written on a value-type parameter. A type parameter written `^T` takes an owner when `T` is a reference type, and is a borrow when `T` is a value type. Passing a value by borrow is the semantic model rather than an optimization; where a read-only borrow is indistinguishable from a copy, the compiler may still pass a small value by copy, the same latitude placement has (§3.5). The distinction becomes observable under concurrent sharing, where a spawned reader sees the borrowed value live (see [`concurrency.md`](concurrency.md) §4.4). ```zane type Car = #struct { engine &Engine; // an `&` field - spare Engine; // a hosting field + spare Engine; // an owning field _value Int; } -// a take: the engine moves into a hosting field of this +// a take: the engine moves into an owning field of this Unit setSpare(this Car, engine ^Engine) mut { this.spare = engine; return Unit(); @@ -281,29 +283,29 @@ Int inspect(this Car, engine Engine) { return this._value + engine.speed; } -// a guest stored into an `&` field: the signature records where it lands +// a reference stored into an `&` field: the signature records where it lands Unit setEngine(this Car, engine &Engine) mut { this.engine = engine; return Unit(); } ``` -`setEngine` stores a guest it was handed. The callee sees two parameters and cannot tell whether the caller's `engine` is hosted above or below the object `this` names, so it does not decide: its signature records that `engine` comes to rest at `this.engine` ([`lifetimes.md`](lifetimes.md) §1.11), and each call substitutes the argument paths it was given and compares owners ([`lifetimes.md`](lifetimes.md) §1.1). +`setEngine` stores a reference it was handed. The callee sees two parameters and cannot tell whether the caller's `engine` is owned above or below the object `this` names, so it does not decide: its signature records that `engine` comes to rest at `this.engine` ([`lifetimes.md`](lifetimes.md) §1.11), and each call substitutes the argument paths it was given and compares scopes ([`lifetimes.md`](lifetimes.md) §1.1). ```zane car Car(...); engine Engine(); -car!setEngine(engine); // → car.engine = engine; one block owns both: legal +car!setEngine(engine); // → car.engine = engine; one scope holds both: legal do() { spare Engine(); car!setEngine(spare); // ILLEGAL: this block does not outlive car's } ``` -**The subject is always a borrow.** `this` — the first parameter, and only it ([`functions.md`](functions.md) §2.1) — is a borrow of the object the method was called on, settled or roaming, and mutable under `mut`. A method never moves `this`, never stores it, and never returns it as `&T`. Nothing is written on `this` to say so, because a subject has no other mode. A verb that hands out a guest into an object takes that object as an `&T` parameter instead: +**The subject is always a borrow.** `this` — the first parameter, and only it ([`functions.md`](functions.md) §2.1) — is a borrow of the object the method was called on, settled or roaming, and mutable under `mut`. A method never moves `this`, never stores it, and never returns it as `&T`. Nothing is written on `this` to say so, because a subject has no other mode. A verb that hands out a reference into an object takes that object as an `&T` parameter instead: ```zane -&Weapon weaponOf(player &Player) => player.weapon // legal: rooted in a guest parameter +&Weapon weaponOf(player &Player) => player.weapon // legal: rooted in an `&T` parameter &Weapon weapon(this Player) => this.weapon // ILLEGAL: this is a borrow ``` @@ -318,12 +320,12 @@ Value types form a closed world of plain value storage. A value-type field may c Here, **downstream** means "through nested value-type fields." The restriction is checked recursively through the full value graph. -The rule is about **copying**, and both banned field kinds fail it the same way. An existing value is copied whole whenever a place expression is bound into a different slot (§2.3). A reference type is the opposite by construction: it exists in order *not* to be copied. It has exactly one host at a time (§2.1), an identity that guests name (§4), and it reaches a new place by being **moved** rather than duplicated (see [`lifetimes.md`](lifetimes.md) §1.2). Copying a value that contained one would have to do one of two things, and both dissolve that: +The rule is about **copying**, and both banned field kinds fail it the same way. An existing value is copied whole whenever a place expression is bound into a different slot (§2.3). A reference type is the opposite by construction: it exists in order *not* to be copied. It has exactly one owner at a time (§2.1), an identity that references name (§4), and it reaches a new place by being **moved** rather than duplicated (see [`lifetimes.md`](lifetimes.md) §1.2). Copying a value that contained one would have to do one of two things, and both dissolve that: -- **Duplicate the object**, minting a second instance with its own identity. Guests to the original would not follow the copy, and "exactly one host" would describe nothing. -- **Share the object**, so two values reach one host. Hosting would no longer be single, and a value would have become a way to alias. +- **Duplicate the object**, minting a second instance with its own identity. References to the original would not follow the copy, and "exactly one owner" would describe nothing. +- **Share the object**, so two values reach one owner. Ownership would no longer be single, and a value would have become a way to alias. -An `&` field fails on the second directly: copying it would duplicate a guest inside a copied value, putting aliasing inside the one world that is defined by having none. `List` is a reference type and is covered by this restriction. `String` and `@primitives$String` are value types with owned backing stores; variable payload size does not itself imply reference semantics ([`types.md`](types.md) §2.7). +An `&` field fails on the second directly: copying it would duplicate a reference inside a copied value, putting aliasing inside the one world that is defined by having none. `List` is a reference type and is covered by this restriction. `String` and `@primitives$String` are value types with owned backing stores; variable payload size does not itself imply reference semantics ([`types.md`](types.md) §2.7). What this closure does **not** bar is **recursion**, and the reason is that a **boxed member** (§3.3) is not a reference-type field: it is out-of-line placement of the member's own declared type, and placement is not language-visible (§3.5). Nothing this rule forbids has entered the value. The recursion rule itself lives in [`adt.md`](adt.md) §4, and the copy that keeps such a value alias-free in §2.3. @@ -352,7 +354,7 @@ type BadRef = struct { } ``` -Downstream enforcement keeps hosting and guest bookkeeping confined to reference types, and — because nothing reachable from a value can be aliased, whether it is stored inline, in a backing store, or behind a box — is what lets a value be shared by snapshot and mutated concurrently under [`concurrency.md`](concurrency.md) §4. +Downstream enforcement keeps ownership and reference bookkeeping confined to reference types, and — because nothing reachable from a value can be aliased, whether it is stored inline, in a backing store, or behind a box — is what lets a value be shared by snapshot and mutated concurrently under [`concurrency.md`](concurrency.md) §4. > **Story:** [`stories/memory.md`](../stories/memory.md#the-value-world-stays-closed-and-placement-stays-the-compilers) — "The value world stays closed, and placement stays the compiler's". > **Story:** [`stories/memory.md`](../stories/memory.md#what-a-copy-is-for-and-the-ban-that-survived-it) — "What a copy is for, and the ban that survived it". @@ -378,14 +380,14 @@ if(runtimeBool()) { ### 3.1 Scope arenas and segmented offsets -Each lexical scope owns an **arena** made from two independent allocation regions: +Each lexical scope has an **arena** made from two independent allocation regions: -- The **fixed-size region** stores materialized value-type slots, statically sized reference-type hosts, and the fixed-size handles — of dynamically-sized types of either kind and of boxed members alike — that are materialized in **scope-level** slots. +- The **fixed-size region** stores materialized value-type slots, statically sized owners, and the fixed-size handles — of dynamically-sized types of either kind and of boxed members alike — that are materialized in **scope-level** slots. - The **dynamic region** stores the payloads behind those handles: the resizable backing stores of types such as `List` and `String`, and the payloads of boxed members (§3.6). A handle that sits *inside* a dynamic payload rather than in a scope slot — a boxed node's own boxed members, an element's owned storage — is part of that payload's block and is not separately placed. Each region is a separate chain of fixed-size **1 MiB chunks** mapped from the OS on demand. A chunk belongs to exactly one region: fixed-size slots and dynamic backing stores never coexist in the same chunk. A region maps no chunk until its first allocation. When its current chunk cannot satisfy an allocation, the runtime maps another chunk for that region, assigns it the next **chunk id**, and makes it current. -Scopes nest last-in-first-out, and their arenas nest with them: both regions of a scope are unmapped in full the moment the scope drains (§3.2, [`lifetimes.md`](lifetimes.md) §2.1). Arena granularity is an implementation choice, like boolean packing (§3.4) and placement (§3.5) — the compiler may fold several lexical scopes into one arena. What the language fixes is the observable behavior: a scope's memory is released together when that scope drains, and no guest ever resolves into released memory. A value that escapes is promoted out of the draining scope first (§3.5); only a roaming host or a value escapes, and nothing guests either. +Scopes nest last-in-first-out, and their arenas nest with them: both regions of a scope are unmapped in full the moment the scope drains (§3.2, [`lifetimes.md`](lifetimes.md) §2.1). Arena granularity is an implementation choice, like boolean packing (§3.4) and placement (§3.5) — the compiler may fold several lexical scopes into one arena. What the language fixes is the observable behavior: a scope's memory is released together when that scope drains, and no reference ever resolves into released memory. A value that escapes is promoted out of the draining scope first (§3.5); only a roaming owner or a value escapes, and nothing references either. ```text one scope arena @@ -398,7 +400,7 @@ An ordinary dynamic allocation never straddles a chunk boundary. A dynamic block A dynamic block larger than 1 MiB is an **oversized span**: a dedicated contiguous OS mapping made from `ceil(block_size / 1 MiB)` consecutive dynamic chunks, all belonging exclusively to that block and assigned consecutive chunk ids. Its handle stores the segmented offset of the span's first byte and its exact block size, alongside the alignment that block was allocated at (§3.2). After resolving that base, element addressing uses an ordinary byte offset across the contiguous mapping. Every constituent chunk also has a directory entry. Returning an oversized span pushes only its base offset onto the exact-size stack; the complete span remains mapped for reuse until the scope drains. -Every chunk draws its id from one chunk directory, so payload locations, dynamic handles, guests, and size-stack entries all use one **`u32` segmented offset**: +Every chunk draws its id from one chunk directory, so payload locations, dynamic handles, references, and size-stack entries all use one **`u32` segmented offset**: ```text u32 segmented offset @@ -410,13 +412,13 @@ Every chunk draws its id from one chunk directory, so payload locations, dynamic Allocations are at least 8-byte aligned, so the low bits count 8-byte words: a 1 MiB chunk holds 2¹⁷ words, so **17 low bits** address any slot in a chunk and the remaining **15 high bits** select one of up to 32768 live chunks — a reach of 32 GiB. The chunk directory maps a chunk id to the chunk's native base address, so an address is materialized only at use, as `directory[chunk id] + word offset × 8`: splitting the `u32` is a shift and a mask, and the directory lookup is one load. -Guests (§4.1), dynamic handles, and size-stack entries (§3.2) use segmented offsets. A payload may occupy segmented offset `0`, so a region's first allocation sits at a chunk base; no offset is reserved, because a guest is always initialized to a host (§2.11). +References (§4.1), dynamic handles, and size-stack entries (§3.2) use segmented offsets. A payload may occupy segmented offset `0`, so a region's first allocation sits at a chunk base; no offset is reserved, because a reference is always initialized to an owner (§2.11). > **Story:** [`stories/memory.md`](../stories/memory.md#the-last-table-problem-and-the-segmented-offset) — "The last table problem, and the segmented offset". ### 3.2 Allocation, reuse, and teardown -The fixed-size region is a pure bump allocator: no size classes, no free list, no coalescing. A host has a fixed-size storage slot, so an overwrite consumes no new space in that region. Reference-type overwrite and move ordering follow §2.2 and §3.5. A materialized **value** slot follows the replacement rule of §2.3: the right-hand side observes the pre-overwrite occupant, and any overlapping replacement is completed before the old value ends. Only then are the old value's owned dynamic blocks — backing stores and boxed payloads alike, recursively — returned to their exact-size stacks and the replacement installed in the same slot. If the compiler proves the replacement does not depend on the current occupant, it may destroy the old value and construct a non-place result directly in that slot. Nothing in the fixed-size region is reclaimed individually — bytes in a slot that cease to be live before the scope drains remain dead space until teardown. +The fixed-size region is a pure bump allocator: no size classes, no free list, no coalescing. An owner has a fixed-size storage slot, so an overwrite consumes no new space in that region. Reference-type overwrite and move ordering follow §2.2 and §3.5. A materialized **value** slot follows the replacement rule of §2.3: the right-hand side observes the pre-overwrite occupant, and any overlapping replacement is completed before the old value ends. Only then are the old value's owned dynamic blocks — backing stores and boxed payloads alike, recursively — returned to their exact-size stacks and the replacement installed in the same slot. If the compiler proves the replacement does not depend on the current occupant, it may destroy the old value and construct a non-place result directly in that slot. Nothing in the fixed-size region is reclaimed individually — bytes in a slot that cease to be live before the scope drains remain dead space until teardown. The dynamic region adds exact-size reuse on top of its bump frontier. Each scope maintains one LIFO **size stack** for every (byte size, alignment) pair that has become reusable. To allocate a dynamic block of size `S` and alignment `A`, the runtime first pops `size_stack[S, A]`; only when that stack is empty does it bump the dynamic frontier, rounding it up to `A` first. Keying on alignment as well as size is what keeps reuse sound now that blocks no longer share one alignment: a block returned by a type needing 8-byte alignment must not be handed to a type needing 16. It never satisfies a request from another size stack and never coalesces neighbouring blocks. @@ -424,22 +426,22 @@ A block's size comes from what it holds, and the two kinds ask for different thi Returning a dynamic block pushes its base segmented offset onto the stack for its own size and alignment. The stacks are shared by all dynamic payloads in the scope, whatever produced them: a 128-byte block previously used by a `List` may later hold string bytes, another list's elements, or a boxed node that happens to match it on both keys. Reuse is therefore exact and never approximate — a freed block serves only a request for the same number of bytes. This suits boxed payloads particularly well, because every instance of one type is the same size (a sum is laid out at its widest case plus tag), so the block a destroyed node returns is precisely what the next node of that type needs. An oversized span participates in the same exact-size policy. -When a scope drains — after all its spawned work completes ([`concurrency.md`](concurrency.md) §4.1) — the runtime unmaps its fixed-size and dynamic chunks in bulk, with no per-object teardown pass threaded through the exit. Logical destruction timing is independent of this: a value dies when its host, container, or scope does ([`lifetimes.md`](lifetimes.md) §2.1); it is the *memory* that is reclaimed together at drain. +When a scope drains — after all its spawned work completes ([`concurrency.md`](concurrency.md) §4.1) — the runtime unmaps its fixed-size and dynamic chunks in bulk, with no per-object teardown pass threaded through the exit. Logical destruction timing is independent of this: a value dies when its owner, container, or scope does ([`lifetimes.md`](lifetimes.md) §2.1); it is the *memory* that is reclaimed together at drain. > **Story:** [`stories/memory.md`](../stories/memory.md#when-the-free-stacks-fragment-and-the-arena-takes-the-scope) — "When the free stacks fragment, and the arena takes the scope". ### 3.3 Value and reference layout follow declaration order -Fields are laid out in declaration order. A value-type instance is stored inline, except for owned backing stores (§3.6) and any members the compiler boxes (below). A statically sized reference-type instance is also stored inline in a fixed-size host slot, so value-type slots and reference-type host slots may sit directly beside each other in the fixed-size region. Reference types differ by identity and hosting semantics, not by requiring a separate indirect allocation. +Fields are laid out in declaration order. A value-type instance is stored inline, except for owned backing stores (§3.6) and any members the compiler boxes (below). A statically sized reference-type instance is also stored inline in a fixed-size owner slot, so value-type slots and owner slots may sit directly beside each other in the fixed-size region. Reference types differ by identity and ownership semantics, not by requiring a separate indirect allocation. -A reference-type instance carries no metadata of its own: a guest names it by its address (§4.1). A dynamically-sized reference type such as `List` occupies a fixed-size handle inline in the same region; only the backing store named by that handle occupies the dynamic region (§3.6). +A reference-type instance carries no metadata of its own: a reference names it by its address (§4.1). A dynamically-sized reference type such as `List` occupies a fixed-size handle inline in the same region; only the backing store named by that handle occupies the dynamic region (§3.6). A **boxed member** is laid out the same way: a fixed-size handle inline, with the instance it names placed in the dynamic region (§3.6). Which members are boxed is [`adt.md`](adt.md) §4's rule — required on a cycle of owning edges, where no finite inline layout exists, and permitted elsewhere per type. §3.5 makes the choice unobservable. Boxing is available on **both** sides of the `#` axis. Two separate questions decide what a boxed member means, and they are answered by **different** types: -- **What the payload is** follows the **member's own declared type**, never the enclosing one. A reference-typed payload is an ordinary reference-type instance with identity: a boxed field takes its root's state as any field does (§2.8.1), and a boxed variant payload is roaming like any payload — being boxed neither grants nor withholds a guest. A value-typed payload is an ordinary value: no identity and nothing to guest. Because boxing is permitted off a cycle (§3.3, [`adt.md`](adt.md) §4), a reference type may box a value-typed member; that stores a plain value out of line and does **not** give it identity. -- **What becomes of the payload when the enclosing instance moves, is copied, or dies** follows the **enclosing type's kind**. A reference type *hosts* what it boxes: it destroys the payload when it dies, and moving it while it roams carries the payload with it (§3.5). A value type *owns* what it boxes: the payload is copied into fresh storage whenever the value is copied (§2.3) and returned when the value dies (§3.2). +- **What the payload is** follows the **member's own declared type**, never the enclosing one. A reference-typed payload is an ordinary reference-type instance with identity: a boxed field takes its root's state as any field does (§2.8.1), and a boxed variant payload is roaming like any payload — being boxed neither grants nor withholds a reference. A value-typed payload is an ordinary value: no identity and nothing to reference. Because boxing is permitted off a cycle (§3.3, [`adt.md`](adt.md) §4), a reference type may box a value-typed member; that stores a plain value out of line and does **not** give it identity. +- **What becomes of the payload when the enclosing instance moves, is copied, or dies** follows the **enclosing type's kind**. A reference type owns what it boxes and *moves* it: it destroys the payload when it dies, and moving it while it roams carries the payload with it (§3.5). A value type owns what it boxes and *copies* it: the payload is copied into fresh storage whenever the value is copied (§2.3) and returned when the value dies (§3.2). Either way the member's declared type is unchanged by being boxed, and the box is placement rather than an extra level of type. @@ -449,17 +451,17 @@ The compiler may pack booleans in structs and arena frames when doing so does no ### 3.5 Statically sized storage uses the fixed-size region -Placement is an implementation decision, not a language-visible property. The arena model places every materialized, statically sized scope slot — value-type storage, a reference-type host, a dynamic type's fixed-size handle, or a boxed member's handle — inline in that scope's fixed-size region. The compiler may keep an unobservable value in registers or otherwise optimize its physical placement, but reference types do not require a separate heap allocation merely because they carry identity. A recursive member is boxed for the opposite reason: not because of which side of the `#` axis its type sits on, but because a finite inline layout does not exist for it (§3.3). +Placement is an implementation decision, not a language-visible property. The arena model places every materialized, statically sized scope slot — value-type storage, an owner, a dynamic type's fixed-size handle, or a boxed member's handle — inline in that scope's fixed-size region. The compiler may keep an unobservable value in registers or otherwise optimize its physical placement, but reference types do not require a separate heap allocation merely because they carry identity. A recursive member is boxed for the opposite reason: not because of which side of the `#` axis its type sits on, but because a finite inline layout does not exist for it (§3.3). -A move transfers a roaming host into a destination host of the **same type** ([`lifetimes.md`](lifetimes.md) §1). Both have the same statically known size, so a move copies the host's inline bytes, its handles among them, into the destination's fixed-size slot. A destination that already holds an object follows §2.2: a settled destination is overwritten in place, and any other destination's occupant is destroyed first. The source slot is spent ([`lifetimes.md`](lifetimes.md) §1.6). Nothing inside a roaming host is guested, so a move updates nothing else. +A move transfers a roaming owner into a destination owner of the **same type** ([`lifetimes.md`](lifetimes.md) §1). Both have the same statically known size, so a move copies the owner's inline bytes, its handles among them, into the destination's fixed-size slot. A destination that already holds an object follows §2.2: a settled destination is overwritten in place, and any other destination's occupant is destroyed first. The source slot is spent ([`lifetimes.md`](lifetimes.md) §1.6). Nothing inside a roaming owner is referenced, so a move updates nothing else. -The **dynamic blocks** the host owns — the backing store behind a `List` or `String` handle, and the payload of a boxed member — stay where they are. A move copies their handles and never their contents, while the scope that holds the blocks outlives the destination host. A move whose destination outlives that scope is an **escape**: a `return` out of the scope that allocated the blocks, or a store into a host declared above it. Before that scope drains, every block the escaping host owns **MUST** reside in a scope that lives as long as the destination, so that no handle ever names released memory. The implementation either relocates each block — allocating an equal-size block or oversized span in such a scope's dynamic region, moving the live contents into it under their ordinary move rules, updating the handle, and returning the old block to its exact-size stack — or allocates the block there in the first place. +The **dynamic blocks** the owner owns — the backing store behind a `List` or `String` handle, and the payload of a boxed member — stay where they are. A move copies their handles and never their contents, while the scope that holds the blocks outlives the destination owner. A move whose destination outlives that scope is an **escape**: a `return` out of the scope that allocated the blocks, or a store into an owner declared above it. Before that scope drains, every block the escaping owner owns **MUST** reside in a scope that lives as long as the destination, so that no handle ever names released memory. The implementation either relocates each block — allocating an equal-size block or oversized span in such a scope's dynamic region, moving the live contents into it under their ordinary move rules, updating the handle, and returning the old block to its exact-size stack — or allocates the block there in the first place. Relocation is **recursive**, because a relocated block may itself own dynamic blocks: a boxed payload holds its own boxed members, and a backing store holds its elements' owned storage. Relocating the root of a roaming recursive structure therefore relocates the whole structure, at a cost proportional to the number of boxed nodes it contains rather than to the root alone. -When a **value itself** is copied into a slot owned by another scope, it reaches that scope by copying rather than moving, and the same recursion applies to its blocks: the copy allocates each backing store and boxed payload afresh in the destination scope's dynamic region and copies into it, so the copy owns storage in the scope that holds it and the source keeps its own (§2.3). This governs the value being copied, not every value-typed payload in sight: one that a reference host owns through a boxed member travels with that host under the relocation rule above, because what becomes of a boxed payload follows the enclosing type's kind (§3.3). +When a **value itself** is copied into a slot owned by another scope, it reaches that scope by copying rather than moving, and the same recursion applies to its blocks: the copy allocates each backing store and boxed payload afresh in the destination scope's dynamic region and copies into it, so the copy owns storage in the scope that holds it and the source keeps its own (§2.3). This governs the value being copied, not every value-typed payload in sight: one that an owner of a reference type holds through a boxed member travels with that owner under the relocation rule above, because what becomes of a boxed payload follows the enclosing type's kind (§3.3). -Placement never changes observable semantics: destruction stays deterministic (see [`lifetimes.md`](lifetimes.md) §2), and a guest names a settled host, which no placement decision moves once it has settled (§4). +Placement never changes observable semantics: destruction stays deterministic (see [`lifetimes.md`](lifetimes.md) §2), and a reference names a settled owner, which no placement decision moves once it has settled (§4). > **Story:** [`stories/memory.md`](../stories/memory.md#the-value-world-stays-closed-and-placement-stays-the-compilers) — "The value world stays closed, and placement stays the compiler's". > **Story:** [`stories/memory.md`](../stories/memory.md#the-region-takes-the-boxes-and-a-box-asks-for-what-it-is) — "The region takes the boxes, and a box asks for what it is". @@ -488,9 +490,9 @@ A list grows according to the following rules: 4. Otherwise, a doubled block of at most 1 MiB is bump-allocated wholly inside one dynamic chunk. A doubled block larger than 1 MiB is allocated as a fresh dedicated oversized span (§3.1). The live elements are relocated into the new block or span. 5. After relocation, the handle's backing-store offset and block size are updated and the old block's base offset is pushed onto the stack for its exact old byte size. -A block never grows in place across a chunk boundary, and an oversized span is never extended in place: further growth relocates into a doubled oversized span after checking that exact-size stack first. Relocation moves or copies elements according to their type's ordinary move rules; the old block becomes reusable only after its previous occupants are no longer live. Guests to the list remain valid because they reach the list's host, whose fixed-size handle now names the current backing store. +A block never grows in place across a chunk boundary, and an oversized span is never extended in place: further growth relocates into a doubled oversized span after checking that exact-size stack first. Relocation moves or copies elements according to their type's ordinary move rules; the old block becomes reusable only after its previous occupants are no longer live. References to the list remain valid because they reach the list's owner, whose fixed-size handle now names the current backing store. -A **boxed member** (§3.3) uses the same two-part representation with a payload that never grows. Its handle records the payload's segmented offset; the payload is one instance of the member's declared type, sized and aligned as §3.2 specifies, and is returned to its size stack when the member's enclosing instance is destroyed. Overwriting the member writes the replacement into the same block, which always fits because both are instances of the member's type, and the replacement's own boxed members are written into the blocks the occupant already holds, recursively (§2.2). A guest into a settled boxed member, or into any member below it, keeps its address. A payload larger than 1 MiB is a dedicated oversized span like any other. None of the growth rules above apply to it: a boxed payload is allocated once and is thereafter only relocated when its roaming host escapes, or allocated afresh by a deep value copy (§2.3, §3.5). +A **boxed member** (§3.3) uses the same two-part representation with a payload that never grows. Its handle records the payload's segmented offset; the payload is one instance of the member's declared type, sized and aligned as §3.2 specifies, and is returned to its size stack when the member's enclosing instance is destroyed. Overwriting the member writes the replacement into the same block, which always fits because both are instances of the member's type, and the replacement's own boxed members are written into the blocks the occupant already holds, recursively (§2.2). A reference into a settled boxed member, or into any member below it, keeps its address. A payload larger than 1 MiB is a dedicated oversized span like any other. None of the growth rules above apply to it: a boxed payload is allocated once and is thereafter only relocated when its roaming owner escapes, or allocated afresh by a deep value copy (§2.3, §3.5). Dynamic chunks and oversized spans begin at cache-line-aligned addresses, and a **growable backing store** — 128 bytes or larger — is cache-line aligned within them. Every other block takes its own type's alignment, which §3.2 applies to reuse and to the frontier alike, so frontier allocations, reused blocks, and dedicated spans all keep their alignment without mixing payloads into fixed-size chunks. @@ -499,25 +501,25 @@ Dynamic chunks and oversized spans begin at cache-line-aligned addresses, and a --- -## 4. Guests +## 4. References -### 4.1 A guest is a settled host's segmented offset +### 4.1 A reference is a settled owner's segmented offset -A guest stores the **`u32` segmented offset** (§3.1) of the settled host it names. At half the width of a 64-bit pointer, twice as many guests fit in a cache line, and resolving one is the chunk-directory load every segmented offset needs. An explicitly declared `&T` slot contains only this offset. +A reference stores the **`u32` segmented offset** (§3.1) of the settled owner it names. At half the width of a 64-bit pointer, twice as many references fit in a cache line, and resolving one is the chunk-directory load every segmented offset needs. An explicitly declared `&T` slot contains only this offset. -A guest minted from a field path such as `car.engine` stores the offset of `engine` inside `car`. A guest copied from another guest copies its offset (§2.6). Nothing is allocated to mint a guest, and nothing is recorded in the host. +A reference minted from a field path such as `car.engine` stores the offset of `engine` inside `car`. A reference copied from another reference copies its offset (§2.6). Nothing is allocated to mint a reference, and nothing is recorded in the owner. ```zane -dps Float = mainWeapon.dps; // mainWeapon's offset → directory → host → dps +dps Float = mainWeapon.dps; // mainWeapon's offset → directory → owner → dps ``` > **Story:** [`stories/memory.md`](../stories/memory.md#guests-without-anchors) — "Guests without anchors". -### 4.2 Why a guest never dangles +### 4.2 Why a reference never dangles -A guest could dangle only if the host it names moved or died while the guest lived. A settled host never moves (§2.1): it is overwritten in place (§2.2), including a boxed member (§3.6), and nothing inside dynamic storage is guested (§2.8.1). It dies when its scope drains, and the store rule ensures every guest to it is owned by that scope or a nested one ([`lifetimes.md`](lifetimes.md) §1.1), so the guest dies no later. A scope with spawned work drains only after that work finishes ([`concurrency.md`](concurrency.md) §4.1). +A reference could dangle only if the owner it names moved or died while the reference lived. A settled owner never moves (§2.1): it is overwritten in place (§2.2), including a boxed member (§3.6), and nothing inside dynamic storage is referenced (§2.8.1). It dies when its scope drains, and the store rule ensures every reference to it is in that scope or a nested one ([`lifetimes.md`](lifetimes.md) §1.1), so the reference dies no later. A scope with spawned work drains only after that work finishes ([`concurrency.md`](concurrency.md) §4.1). -An overwrite destroys the old occupant while guests to the slot remain. They name the slot, not the occupant, and observe the replacement (§2.2). This is the only change of object a guest ever sees. +An overwrite destroys the old occupant while references to the slot remain. They name the slot, not the occupant, and observe the replacement (§2.2). This is the only change of object a reference ever sees. > **Story:** [`stories/memory.md`](../stories/memory.md#guests-without-anchors) — "Guests without anchors". @@ -525,16 +527,16 @@ An overwrite destroys the old occupant while guests to the slot remain. They nam ## 5. Language Comparisons -### 5.1 Hosting and references +### 5.1 Ownership and references | Feature | Zane | C++ `unique_ptr` | C++ `shared_ptr` | Rust | |---|---|---|---|---| -| Single host by default | ✅ | ❌ | ❌ | ✅ | -| Non-hosting guests as explicit opt-in | ✅ | ⚠️ Raw pointers | ⚠️ `weak_ptr` | ✅ | +| Single owner by default | ✅ | ❌ | ❌ | ✅ | +| Non-owning references as explicit opt-in | ✅ | ⚠️ Raw pointers | ⚠️ `weak_ptr` | ✅ | | Lifetime annotations required | ❌ | ❌ | ❌ | ✅ | | Reference counting required | ❌ | ❌ | ✅ | ⚠️ `Rc`/`Arc` only | -| Guested objects never move | ✅ settled hosts | ❌ | ❌ | ⚠️ only while borrowed | -| Host overwrite keeps existing guests valid | ✅ in place | ❌ | ❌ | ⚠️ heavily restricted by borrow checking | +| Referenced objects never move | ✅ settled owners | ❌ | ❌ | ⚠️ only while borrowed | +| Owner overwrite keeps existing references valid | ✅ in place | ❌ | ❌ | ⚠️ heavily restricted by borrow checking | ### 5.2 Allocation @@ -550,31 +552,31 @@ An overwrite destroys the old occupant while guests to the slot remain. They nam | Concept | Rule | |---|---| -| Settled host | May be guested; never moves; a bare reference-type symbol, or a field of a settled root | -| Roaming host | May move; nothing guests it or anything inside it; written `^T`, and every list element and variant payload | -| Settling | A roaming host settles by moving into a settled place; a settled host never roams again | -| Hosting storage | Reference-typed symbols, fields, and container elements are directly initialized and may later be overwritten | -| Settled overwrite | Destroys the old occupant and writes the replacement at the same address, reusing the block of every boxed member reached through struct fields and `ArrayRef` elements; guests to the slot or any of its fields observe the replacement | +| Settled owner | May be referenced; never moves; a bare reference-type symbol, or a field of a settled root | +| Roaming owner | May move; nothing references it or anything inside it; written `^T`, and every list element and variant payload | +| Settling | A roaming owner settles by moving into a settled place; a settled owner never roams again | +| Owning storage | Reference-typed symbols, fields, and container elements are directly initialized and may later be overwritten | +| Settled overwrite | Destroys the old occupant and writes the replacement at the same address, reusing the block of every boxed member reached through struct fields and `ArrayRef` elements; references to the slot or any of its fields observe the replacement | | Value type | Mutable in place through a borrowed `mut` subject; storage may also be overwritten freely | | Value construction | A non-place value expression constructs directly in its eventual destination, recursively through nested fresh results; only an existing place is copied | | Value overwrite | The right-hand side observes the pre-overwrite value; an overlapping replacement is completed before the old value and its owned blocks are destroyed | | Value copy | Copies the whole existing value: inline bytes, plus a fresh allocation and recursive copy of every backing store and boxed payload the value owns, so two values never share storage; a copy from a place never read again may be a move | -| `&` (guest) | Guest-only non-hosting storage naming a settled host; may be repointed, copied by value, and returned, but can never host a `T` | -| Spent host slot | After a roaming host's value moves out, the slot is spent and keeps enough storage for a store to refill it | +| `&` (reference) | Reference-only non-owning storage naming a settled owner; may be repointed, copied by value, and returned, but can never own a `T` | +| Spent owner slot | After a roaming owner's value moves out, the slot is spent and keeps enough storage for a store to refill it | | Place expression | Existing storage: a named symbol, a field access of a place, a place-projection subscript of a place, or an `&` parameter | -| New `&` value | May be minted only from a settled place: a bare settled symbol, or a path from a settled root or an `&T` parameter through struct fields and `ArrayRef` elements only; roaming hosts, list elements, variant payloads, and temporaries are rejected | -| `ArrayRef` element | Fixed storage: takes its root's state, may be guested under a settled root, is overwritten in place, and is never moved out | +| New `&` value | May be minted only from a settled place: a bare settled symbol, or a path from a settled root or an `&T` parameter through struct fields and `ArrayRef` elements only; roaming owners, list elements, variant payloads, and temporaries are rejected | +| `ArrayRef` element | Fixed storage: takes its root's state, may be referenced under a settled root, is overwritten in place, and is never moved out | | Field of a roaming root | May be moved out; the root is partly spent until refilled; a field is never declared roaming | -| Borrow | Non-hosting, non-escaping access to a caller's storage for the duration of a call; not storable, not returnable, not a guest source, not a move-source | +| Borrow | Non-owning, non-escaping access to a caller's storage for the duration of a call; not storable, not returnable, not a reference source, not a move-source | | Value-type parameter | Always a read-only borrow; copied only when the parameter is itself bound into a fresh slot | -| Reference-type parameter | `T` borrows; `^T` takes a roaming host or temporary, spending the caller's symbol; `&T` takes a guest and leaves the caller a full host | +| Reference-type parameter | `T` borrows; `^T` takes a roaming owner or temporary, spending the caller's symbol; `&T` takes a reference and leaves the caller a full owner | | Subject | Always a borrow, mutable under `mut`; never moved, stored, or returned as `&T` | | Value-downstream enforcement | Value types may contain only value types, value-type primitives among them, transitively — never a reference-type or `&` field, because a reference type is made to be moved rather than copied; recursion is **not** barred, since a boxed member is placement rather than a reference-type field | | `&` targets reference types | An `&T` requires `T` to be a reference type; a value is shared by copy or borrow, never by a stored `&` | | Symbol declaration | Must be directly initialized | -| Reference-type placement | Inline storage is bump-allocated in the creating scope's fixed-size region; moving a roaming host copies its inline bytes and handles, and its dynamic blocks stay put unless it escapes the scope holding them, which must first leave every block in a scope that lives as long as the destination | -| Boxed member | A member whose type can lead back to the enclosing type is stored as a fixed-size handle inline with its enclosing instance, while the instance the handle names lives in the dynamic region; required on a containment cycle, permitted elsewhere, and nothing marks it in the source. In a reference type it is a **hosting** member; in a value type the value owns it outright and deep-copies it. An overwrite reuses its block | -| `&` representation | A guest is the `u32` segmented offset of the settled host it names | +| Reference-type placement | Inline storage is bump-allocated in the creating scope's fixed-size region; moving a roaming owner copies its inline bytes and handles, and its dynamic blocks stay put unless it escapes the scope holding them, which must first leave every block in a scope that lives as long as the destination | +| Boxed member | A member whose type can lead back to the enclosing type is stored as a fixed-size handle inline with its enclosing instance, while the instance the handle names lives in the dynamic region; required on a containment cycle, permitted elsewhere, and nothing marks it in the source. In a reference type it moves with its enclosing instance; in a value type it is deep-copied with it. An overwrite reuses its block | +| `&` representation | A reference is the `u32` segmented offset of the settled owner it names | | Addressing | Every chunk shares one `u32` segmented-offset directory; 8-byte-aligned offsets reach 32 GiB across up to 32768 1 MiB chunks | | Dynamic allocation | Exact-size stack first, frontier second; a growable backing store uses power-of-two sizes from 128 bytes because it doubles, while a boxed payload asks for exactly its type's size and has no class; blocks above 1 MiB use dedicated contiguous oversized spans | | Dynamic-block alignment | A growable backing store is cache-line aligned; a boxed payload takes its type's alignment; the frontier is rounded up before it is bumped (§3.6) | diff --git a/spec/syntax.md b/spec/syntax.md index b2e73ba..886e9a2 100644 --- a/spec/syntax.md +++ b/spec/syntax.md @@ -164,13 +164,13 @@ TypeName ^TypeName ``` -`&TypeName` is a **guest** type. It is legal in storage sites (local-variable declarations, fields, and nested storage types such as the example below), as well as in function and constructor parameter positions and return-type positions. +`&TypeName` is a **reference** type. It is legal in storage sites (local-variable declarations, fields, and nested storage types such as the example below), as well as in function and constructor parameter positions and return-type positions. ```zane List<&Node> ``` -`^TypeName` is a **roaming** host of a reference type. It is legal on a local-variable declaration, a parameter, and a return type, and nowhere else: never on a field, and never inside another type's arguments. `^` and `&` are the only markers a type may carry, and never together. +`^TypeName` is a **roaming** owner of a reference type. It is legal on a local-variable declaration, a parameter, and a return type, and nowhere else: never on a field, and never inside another type's arguments. `^` and `&` are the only markers a type may carry, and never together. ```zane spare ^Engine = Engine(); @@ -178,7 +178,7 @@ spare ^Engine = Engine(); Unit park(this Garage, car ^Car) mut { ... } ``` -See [`memory.md`](memory.md) §2.1 for settled and roaming hosts, and §2.9 for the semantics of the three passing modes. +See [`memory.md`](memory.md) §2.1 for settled and roaming owners, and §2.9 for the semantics of the three passing modes. ### 2.4 Type expressions @@ -386,9 +386,9 @@ type Cell = #struct { value Int; } // reference product type, decl type Tree = #variant { leaf Int; node Tree; } // reference sum type; `node` recurses ``` -`node` is written as an ordinary hosting member. The compiler boxes such a member because no finite inline layout exists for it — nothing is written for that, and it is not an `&` (see [`adt.md`](adt.md) §4). A value type may recurse the same way; its boxed member is owned by the value and deep-copied with it (see [`memory.md`](memory.md) §2.3). +`node` is written as an ordinary owning member. The compiler boxes such a member because no finite inline layout exists for it — nothing is written for that, and it is not an `&` (see [`adt.md`](adt.md) §4). A value type may recurse the same way; its boxed member is owned by the value and deep-copied with it (see [`memory.md`](memory.md) §2.3). -`&` combines with a reference type and never with a bare value type: an `&T` requires `T` to be a reference type — a declared `#struct`/`#variant`/`#enum` — so a stored **guest** is written `&Cell` or `&Tree` (see [`memory.md`](memory.md) §2.4). See [`types.md`](types.md) §2.1 for the semantics. +`&` combines with a reference type and never with a bare value type: an `&T` requires `T` to be a reference type — a declared `#struct`/`#variant`/`#enum` — so a stored **reference** is written `&Cell` or `&Tree` (see [`memory.md`](memory.md) §2.4). See [`types.md`](types.md) §2.1 for the semantics. ### 2.15 String literal forms @@ -426,7 +426,7 @@ ReturnType name(param T Type, ...) { body } ReturnType name(param Container, ...) { body } ``` -A **reference-type** parameter independently selects one of the three passing modes (see [`memory.md`](memory.md) §2.9): bare `ParamType` borrows, `^ParamType` takes the host, `&ParamType` takes a guest. A **value-type** parameter has no such choice — it is always a read-only borrow — so neither `^` nor `&` is written on one. A reference-typed return type is written `^ReturnType` or `&ReturnType`; a bare one is ill-formed, because a borrow is never returned. +A **reference-type** parameter independently selects one of the three passing modes (see [`memory.md`](memory.md) §2.9): bare `ParamType` borrows, `^ParamType` takes the owner, `&ParamType` takes a reference. A **value-type** parameter has no such choice — it is always a read-only borrow — so neither `^` nor `&` is written on one. A reference-typed return type is written `^ReturnType` or `&ReturnType`; a bare one is ill-formed, because a borrow is never returned. A function, method, or constructor has no `<>` parameter header. It introduces a type or number parameter inline within its value parameters, at the parameter's first **marked** occurrence — on a value parameter's type (`param T Type`) or inside a value parameter's nested type (`param Container`) — and references it bare elsewhere, including in positions written earlier such as the return type. Inline parameters are inferred from the value arguments at the call; the same `Type` / `@concepts$Int` concepts are used as in a type definition's header (§2.5). See [`generics.md`](generics.md) §3 and §5. @@ -452,7 +452,7 @@ ReturnType name(this SubjectType, param ParamType, ...) `this` is legal only in the first parameter position. A declaration is a method if and only if its first parameter is named `this`. -The subject takes **no** marker, for either kind of type: neither `^` nor `&` is written on `this`. The subject is always a borrow of the caller's value or host, mutable when the method is `mut`, and never stored or returned as `&T`. See [`functions.md`](functions.md) §2.4. +The subject takes **no** marker, for either kind of type: neither `^` nor `&` is written on `this`. The subject is always a borrow of the caller's value or owner, mutable when the method is `mut`, and never stored or returned as `&T`. See [`functions.md`](functions.md) §2.4. `=> expr` returns `expr`, including when `expr` has type `Unit`. diff --git a/spec/types.md b/spec/types.md index 53024a7..149a1fb 100644 --- a/spec/types.md +++ b/spec/types.md @@ -2,7 +2,7 @@ This document specifies Zane's data types: fundamental, value, and reference types; the `#` modifier; fields; constructors; `type` and `alias` declarations; and the `init{ }` expression. Methods and other behavior live in [`functions.md`](functions.md). -> **See also:** [`memory.md`](memory.md) §2 for hosting rules. [`functions.md`](functions.md) for methods and functions. [`syntax.md`](syntax.md) §1 and §3 for declaration grammar. +> **See also:** [`memory.md`](memory.md) §2 for ownership rules. [`functions.md`](functions.md) for methods and functions. [`syntax.md`](syntax.md) §1 and §3 for declaration grammar. --- @@ -25,7 +25,7 @@ Zane keeps data layout and construction separate from behavior. Every mould is a **value mould** unless it is marked with `#`, which makes it a **reference mould**; these are its **value form** and its **reference form**. A type declared with a value mould is a **value type**; one declared with a reference mould is a **reference type**. An intrinsic type's kind is fixed by the compiler instead: `@primitives$Int`, `@primitives$Float`, `@primitives$Bool`, `@primitives$String` (§2.7), and `@primitives$Array` are value types, and `@primitives$ArrayRef`, `@primitives$List` ([`generics.md`](generics.md) §8), and the runtime types ([`effects.md`](effects.md) §6.6) are reference types. This value/reference axis is orthogonal to the *shape* of the mould (such as a product `struct` or a sum `variant`, see §2.5). For the product shape, `struct` is the value mould and `#struct` the reference mould. The `#` mark applies only to a **mould** — `#struct`, `#variant`, or `#enum` (see [`adt.md`](adt.md) §2 and §3 for `#enum` and `#variant`) — and only where a type is declared (§5.3). A reference type is a **distinct type** from any value type; it reuses only the field layout of its mould and otherwise has its own identity, its own constructors, and its own methods (see [`memory.md`](memory.md) §2). -A **value type** is copied on assignment, has no identity, and is *transitively* a value: it may contain only other value types, never a reference-type or `&` field (§2.2, [`memory.md`](memory.md) §2.10). A **reference type** has single hosting and stable identity, follows the rules in [`memory.md`](memory.md) §2, may be aliased through `&`, may hold reference-type and `&` fields, and is moved rather than copied. Either kind may **recurse**, through a member the compiler boxes (see [`adt.md`](adt.md) §4). Placement — stack or heap — is an unobservable implementation choice for both kinds (see [`memory.md`](memory.md) §3.5). +A **value type** is copied on assignment, has no identity, and is *transitively* a value: it may contain only other value types, never a reference-type or `&` field (§2.2, [`memory.md`](memory.md) §2.10). A **reference type** has single ownership and stable identity, follows the rules in [`memory.md`](memory.md) §2, may be aliased through `&`, may hold reference-type and `&` fields, and is moved rather than copied. Either kind may **recurse**, through a member the compiler boxes (see [`adt.md`](adt.md) §4). Placement — stack or heap — is an unobservable implementation choice for both kinds (see [`memory.md`](memory.md) §3.5). ```zane package Graph @@ -42,7 +42,7 @@ type Node = #struct { // reference type: identity, may hold `&`, moved not ### 2.2 Value types are transitive and mutable in place -A value-type body contains only field declarations, stored inline apart from any member the compiler boxes (see [`memory.md`](memory.md) §3.3). A value type **MUST NOT** contain a reference-type or `&` field, and this holds transitively: a value type reachable through a value type must itself be a value type (see [`memory.md`](memory.md) §2.10). The restriction is what makes a value copyable and shareable-by-snapshot with no hosting bookkeeping. It does not stop a value type from containing *itself* (see [`adt.md`](adt.md) §4). +A value-type body contains only field declarations, stored inline apart from any member the compiler boxes (see [`memory.md`](memory.md) §3.3). A value type **MUST NOT** contain a reference-type or `&` field, and this holds transitively: a value type reachable through a value type must itself be a value type (see [`memory.md`](memory.md) §2.10). The restriction is what makes a value copyable and shareable-by-snapshot with no ownership bookkeeping. It does not stop a value type from containing *itself* (see [`adt.md`](adt.md) §4). A value is **mutable in place**: a `mut` method may write its fields, because the subject is a *borrow* of the caller's storage rather than a copy (see [`effects.md`](effects.md) §2.3 and [`functions.md`](functions.md) §2.4). A value's storage slot may also be overwritten wholesale. @@ -96,7 +96,7 @@ The `#` modifier (§2.1) is the other axis: `struct`/`#struct` are the product p The language names none of them. The control-flow intrinsics take storage primitives or no arguments at all ([`control-flow.md`](control-flow.md) §4.1), so no construct in the grammar depends on a declaration in any package. -What ties the fundamental types to ordinary source is `core`'s own declarations. It defines them over storage primitives in the `@primitives$` namespace, and it declares the implicit constructors that carry a value into one: from the compiler concept types that represent source literals, so `20` becomes an `Int`, `2.5` a `Float`, and `"a"` a `String` at a coercion site, and from a fundamental type back to its own storage primitive — `Bool` to `@primitives$Bool`, which is what carries a condition into `@controlflow$branch`, and `Int` to `@primitives$Int`, which is what carries a count into `@controlflow$repeat`. `core` also declares `String` as a value type over `@primitives$String`, with an implicit constructor from `@concepts$String` and one back to `@primitives$String`. Strings follow the ordinary deep-copy and borrowed-parameter rules ([`memory.md`](memory.md) §2.3 and §2.9); they have no hosting identity and cannot be targeted by an `&` guest. `core`'s `Unit` is declared over `@primitives$Unit` in the same way. Explicit `Bool(true)` and `Int(20)` construction remains legal. None of this is special-cased: the conversions are ordinary implicit constructors under §4, visible by the home-package rule of §4.5, and another package may declare the same kind of conversion for its own types. +What ties the fundamental types to ordinary source is `core`'s own declarations. It defines them over storage primitives in the `@primitives$` namespace, and it declares the implicit constructors that carry a value into one: from the compiler concept types that represent source literals, so `20` becomes an `Int`, `2.5` a `Float`, and `"a"` a `String` at a coercion site, and from a fundamental type back to its own storage primitive — `Bool` to `@primitives$Bool`, which is what carries a condition into `@controlflow$branch`, and `Int` to `@primitives$Int`, which is what carries a count into `@controlflow$repeat`. `core` also declares `String` as a value type over `@primitives$String`, with an implicit constructor from `@concepts$String` and one back to `@primitives$String`. Strings follow the ordinary deep-copy and borrowed-parameter rules ([`memory.md`](memory.md) §2.3 and §2.9); they have no owning identity and cannot be targeted by an `&` reference. `core`'s `Unit` is declared over `@primitives$Unit` in the same way. Explicit `Bool(true)` and `Int(20)` construction remains legal. None of this is special-cased: the conversions are ordinary implicit constructors under §4, visible by the home-package rule of §4.5, and another package may declare the same kind of conversion for its own types. `Int` converts only from `@concepts$Int`, so a float literal never becomes an `Int`: `Int(2.5)` is a type error, and so is a `2.5` passed where an `Int` is expected. `Float` converts only from `@concepts$Float`, so `Float(2.0)` is legal and `Float(2)` is a type error. Which literal a numeric type accepts is decided by the conversions its package declares, so a package declaring its own numeric type chooses the same way ([`lexical.md`](lexical.md) §7). @@ -158,7 +158,7 @@ The numeric arguments are values of leaf concept types, so they are known at com big @primitives$Int(99999999999999999999); // ILLEGAL: out of range for @primitives$Int ``` -`@primitives$String` is a **string primitive**: a value type whose fixed-size handle records the segmented offset of its owned bytes in the dynamic region ([`memory.md`](memory.md) §3.6), their length in bytes, and the backing block's allocation metadata. The bytes carry no terminator. A consumer that needs a terminator adds one itself. A copy owns independent bytes; a method borrows the value under the ordinary rules. The primitive has no identity and cannot be targeted by an `&` guest. +`@primitives$String` is a **string primitive**: a value type whose fixed-size handle records the segmented offset of its owned bytes in the dynamic region ([`memory.md`](memory.md) §3.6), their length in bytes, and the backing block's allocation metadata. The bytes carry no terminator. A consumer that needs a terminator adds one itself. A copy owns independent bytes; a method borrows the value under the ordinary rules. The primitive has no identity and cannot be targeted by an `&` reference. The compiler-provided string constructor concatenates the string concept's literal fragments and interpolated string values in their written order (§2.8). It does not interpret any remaining backslash sequences. A package constructor accepting the concept may instead interpret its literal fragments, for example as regex syntax or text escapes. Interpolated values remain distinct from those fragments and are not rescanned as source escapes or interpolation. `@runtime$Console` borrows the resulting primitive and writes its bytes as they are ([`effects.md`](effects.md) §6.6). @@ -180,7 +180,7 @@ A string literal, interpolated or not, carries `@concepts$String`. It is a conce At each `\%var`, the compiler resolves `var` as a value symbol in the caller's scope. The site requires `@primitives$String` and is a coercion site (§4.2): a primitive string is accepted directly, or exactly one visible applicable implicit constructor converts the source to that primitive. The ordinary source restriction (§4.4), home-package rule (§4.5), ambiguity check, and no-chaining rule (§4.3) all apply. There is no automatic numeric formatting or special conversion from a reference type. -Each interpolation is evaluated once, in written order, when the containing literal is evaluated. The concept captures a copy of the resulting primitive string under [`memory.md`](memory.md) §2.3, not a guest or a deferred lookup of the symbol. Later changes to the source do not change the captured value. Conversions follow the ordinary effect and abort-handling rules. The concept does not require an intermediate concatenated storage string; its consumer may build directly in its eventual destination. +Each interpolation is evaluated once, in written order, when the containing literal is evaluated. The concept captures a copy of the resulting primitive string under [`memory.md`](memory.md) §2.3, not a reference or a deferred lookup of the symbol. Later changes to the source do not change the captured value. Conversions follow the ordinary effect and abort-handling rules. The concept does not require an intermediate concatenated storage string; its consumer may build directly in its eventual destination. ```zane name String("enrique"); @@ -267,7 +267,7 @@ Vector{x Int; y Int;} { } ``` -This form is the canonical constructor syntax when the constructor parameters map directly to fields. A field-constructor entry of a reference type is a parameter like any other ([`memory.md`](memory.md) §2.9): an entry that fills a hosting field is written `^T` and takes the host, and one that fills an `&` field is written `&T`. +This form is the canonical constructor syntax when the constructor parameters map directly to fields. A field-constructor entry of a reference type is a parameter like any other ([`memory.md`](memory.md) §2.9): an entry that fills an owning field is written `^T` and takes the owner, and one that fills an `&` field is written `&T`. Field-constructor entries may also declare default values. They use the same initialized declaration forms as ordinary storage declarations. A call may omit any field whose constructor entry provides one: @@ -376,7 +376,7 @@ Constructors are not methods. They create new values rather than mutating an exi ### 3.9 `&` fields require `&` constructor parameters -An `&` field is legal only in a reference type (`#struct`/`#variant`), since a value type is transitively value (§2.2). A constructor that assigns a value to an `&` field must declare the corresponding parameter as `&T`. A borrow `T` is never stored, and a taken `^T` is roaming, which nothing guests ([`memory.md`](memory.md) §2.9). The caller must then supply a guest under [`memory.md`](memory.md) §2.8: either an existing `&T` value or a settled place that may mint one. A bare settled symbol, or a path from a settled root through struct fields and `ArrayRef` elements, may mint a guest; a roaming host, a list element, a variant payload, and any temporary may not. A read whose value is already `&T`, such as `weapons[1]` for `List<&Weapon>`, remains legal because it copies the stored guest rather than minting one from the element slot. +An `&` field is legal only in a reference type (`#struct`/`#variant`), since a value type is transitively value (§2.2). A constructor that assigns a value to an `&` field must declare the corresponding parameter as `&T`. A borrow `T` is never stored, and a taken `^T` is roaming, which nothing references ([`memory.md`](memory.md) §2.9). The caller must then supply a reference under [`memory.md`](memory.md) §2.8: either an existing `&T` value or a settled place that may mint one. A bare settled symbol, or a path from a settled root through struct fields and `ArrayRef` elements, may mint a reference; a roaming owner, a list element, a variant payload, and any temporary may not. A read whose value is already `&T`, such as `weapons[1]` for `List<&Weapon>`, remains legal because it copies the stored reference rather than minting one from the element slot. ```zane package Vehicle @@ -402,20 +402,20 @@ Call sites: ```zane garage Garage(); -car Car(garage.spare); // legal: a field access is a guest source +car Car(garage.spare); // legal: a field access is a reference source ``` ```zane engine Engine(); -car Car(engine); // legal: a bare settled symbol is a guest source +car Car(engine); // legal: a bare settled symbol is a reference source car Car(Engine()); // ILLEGAL: a temporary cannot initialize an `&` field ``` -What still constrains such a field is lifetime, not source: the object it points at must have an owner that outlives the owner of the place holding the `&` ([`lifetimes.md`](lifetimes.md) §1.1). A field takes its root symbol's owner, so that comparison does not stop at construction — every later store of the containing value asks it again, over the guests that value carries ([`lifetimes.md`](lifetimes.md) §1.10). A constructor cannot make the comparison itself, because `init{ }` has no owner until the caller says where the object goes; it records which parameters land in `&` fields and each call settles it ([`lifetimes.md`](lifetimes.md) §1.11). Recursion is not one of these cases at all: a recursive member is an ordinary owning field the compiler boxes, so it needs no `&` and no guest source (see [`adt.md`](adt.md) §4). +What still constrains such a field is lifetime, not source: the object it points at must have an owner whose scope outlives the scope of the place holding the `&` ([`lifetimes.md`](lifetimes.md) §1.1). A field takes its root symbol's scope, so that comparison does not stop at construction — every later store of the containing value asks it again, over the references that value carries ([`lifetimes.md`](lifetimes.md) §1.10). A constructor cannot make the comparison itself, because `init{ }` has no scope until the caller says where the object goes; it records which parameters land in `&` fields and each call settles it ([`lifetimes.md`](lifetimes.md) §1.11). Recursion is not one of these cases at all: a recursive member is an ordinary owning field the compiler boxes, so it needs no `&` and no reference source (see [`adt.md`](adt.md) §4). > **Story:** [`stories/memory.md`](../stories/memory.md#bare-symbols-become-guest-sources-again) — "Bare symbols become guest sources again". -A reference type whose fields are all plain hosts takes them as `^T` parameters, moving each into the object it builds: +A reference type whose fields are all plain owners takes them as `^T` parameters, moving each into the object it builds: ```zane type Car = #struct { @@ -678,10 +678,10 @@ Intent lives entirely in the keyword — `type` versus `alias` — not in the pu | Mould | One of the three type-shaping forms — `struct`, `variant`, or `enum`; each has a value form and a `#` reference form; appears only as a `type`/`alias` right-hand side, so every constructible type is named | | Use-site types | A field, parameter, or return type names a declared type or an instantiation (`Weapon`, `Vector`, `&Node`); a mould appears only as a `type`/`alias` right-hand side | | Value type | Copied on assignment; transitively value (no reference-type or `&` field, anywhere downstream); mutable in place through a borrowed `mut` subject; storage may also be overwritten wholesale | -| Reference type (`#`) | Single hosting and stable identity; may hold reference-type and `&` fields; moved rather than copied; placement is unobservable | +| Reference type (`#`) | Single ownership and stable identity; may hold reference-type and `&` fields; moved rather than copied; placement is unobservable | | Fundamental type | `Int`, `Float`, `Bool`, `String`, `Unit`, `Array`, `ArrayRef`, or `List`; declared by `core`, which is an ordinary package with no standing in the language | | Literal storage primitive | `@primitives$Int`, `@primitives$Float`, or `@primitives$String`; each has one compiler-provided constructor, not `implicit`, taking its literal's concept type; packages may declare implicit conversions to primitives under §4; a literal the primitive cannot represent is a compile-time error | -| String primitive | `@primitives$String`: a value type with owned bytes in the dynamic region and a fixed-size handle; no terminator or stored guest; copies are deep | +| String primitive | `@primitives$String`: a value type with owned bytes in the dynamic region and a fixed-size handle; no terminator or stored reference; copies are deep | | String interpolation | `\%var` captures a copied `@primitives$String`, accepting a direct primitive or one ordinary implicit conversion; the result remains a string concept and may carry runtime values | | `Unit` | Empty `core` value type; `Unit()` constructs its sole value, which may be stored or used as a generic argument | | Field visibility | Names starting with `_` are private to `this`-parameter methods on the subject type; all other names are public | @@ -689,7 +689,7 @@ Intent lives entirely in the keyword — `type` versus `alias` — not in the pu | Field constructor | Declares field parameters directly, may assign default values, and may use `init{field;}` shorthand | | Implicit constructor | Single-parameter constructor marked `implicit`; inserted at callable arguments, named field-constructor entries, enum-map entries, and string interpolation sites — never at declarations, assignments, stores, `return`, or the `init{field = value;}` inside a constructor body; no field-constructor form; source type must be a value type or compiler concept; destination may be a value type, a reference type, or a storage primitive; orphan rule applies | | `&` constructor parameter | Caller must supply an allowed `&` source; callee may store into `&` fields | -| `^T` constructor parameter | Takes a roaming host or a temporary; callee moves it into a hosting field | +| `^T` constructor parameter | Takes a roaming owner or a temporary; callee moves it into an owning field | | Plain `T` constructor parameter | A borrow: read only, never stored; a value-type parameter is always one | | `Type` / `@concepts$Int` constructor parameter | Accepts a type or a compile-time integer; inferred from inline introduction or passed explicitly as a value parameter | | `type` declaration | Introduces a new distinct type, structurally equal to its right-hand side but not interchangeable with it | diff --git a/stories/lifetimes.md b/stories/lifetimes.md index 0b91c6e..b5b1959 100644 --- a/stories/lifetimes.md +++ b/stories/lifetimes.md @@ -296,6 +296,9 @@ What we had actually been doing, without naming it, was recognising exactly one Give it a name — an **owner** — and say where each place gets one. A symbol is owned by its declaring block. A field or element is owned by its root symbol's owner, never its own. A parameter and an `init{ }` have no owner in the body at all; each stands for a path in the caller's frame. Then the whole of it is one sentence: a store is legal when every host the stored value names, directly or through a guest it carries, has an owner that outlives the destination's owner. +> [!NOTE] +> Superseded: this lifetime is now a place's **scope**, and "owner" names the slot that holds an object. See "[Owner and reference replace host and guest, and the store rule compares scopes](memory.md#owner-and-reference-replace-host-and-guest-and-the-store-rule-compares-scopes)". + The four raise forms evaporate, and not by being absorbed into a longer sentence. They were four *syntactic occasions* on which a value changes lifetime, and a value changes lifetime by being **stored** — an assignment, a move, a return, an argument. There was never anything to enumerate. We had enumerated because we were looking for the places a check could go stale, which is a question you only have to ask if you believe some checks run once and stay believed. Two things we had assumed were consequences of the root rule turned out to be consequences of nothing, and both were losses. diff --git a/stories/memory.md b/stories/memory.md index 72021b1..f6709c9 100644 --- a/stories/memory.md +++ b/stories/memory.md @@ -72,7 +72,7 @@ We nearly rejected it too. A tether, we worried, sounds like it *keeps the far t So the value-side handle is a **tether** ([`memory.md` §2.4](https://github.com/zane-lang/spec/blob/93bd2f0036b011c9fc876e785bff0d6a4d09465a/spec/memory.md#24--is-a-tether-non-owning-storage)), and the type axis keeps "reference type" to itself. The name pays a small resonance back on a second look, the kind [the naming guide](../contributing/naming-terms.md) hopes for: a tether is *slack* — it does not pull the owner anywhere, it keeps station beside it, which is exactly how a non-owning handle sits next to the value it watches. And it joins a set already speaking the same dialect — a `verb` acts, a `mould` shapes, a value is `borrow`ed and given back, an `anchor` holds fast — so now a `tether` ties to the anchor without owning it. > [!NOTE] -> Superseded: the runtime no longer has tethers or anchors; a guest is a settled host's segmented offset. See "[Guests without anchors](#guests-without-anchors)". +> Superseded: the runtime no longer has tethers or anchors, and the `&` is now a **reference**, sharing its root with *reference type* on purpose. See "[Guests without anchors](#guests-without-anchors)" and "[Owner and reference replace host and guest, and the store rule compares scopes](#owner-and-reference-replace-host-and-guest-and-the-store-rule-compares-scopes)". The cost is the ordinary cost of any coined term: a reader meets "tether" and must be told once what it is, where "ref" would have passed without comment — at the price of the collision that started this. And the rename reaches sideways into ordinary words: an owner with a tether on it is now "tethered", one with none "untethered", which quietly retires "referenced" from the value side to keep the split clean. The earlier chapters of this very story still say "ref", because they were written when that was the word, and the history is left standing rather than back-dated; this chapter is where the name changed, not a pretence that it was always so. @@ -146,6 +146,9 @@ The source pair is now **host** and **guest**. A host is the symbol, field, or c The runtime keeps **anchor** and **tether**. Each guest is represented by a tether that resolves through an anchor; moving or rehosting the object updates the anchor, so existing tethers keep working. That vocabulary remains a natural mechanical picture, but it no longer leaks upward into source semantics. The concise model is: *an object lives in a host; a guest may access it; internally, the guest's tether follows the object through its anchor.* +> [!NOTE] +> Superseded: the source pair is now **owner** and **reference**. See "[Owner and reference replace host and guest, and the store rule compares scopes](#owner-and-reference-replace-host-and-guest-and-the-store-rule-compares-scopes)". + > [!NOTE] > Superseded: the runtime keeps neither; a guest is a settled host's segmented offset. See "[Guests without anchors](#guests-without-anchors)". @@ -433,6 +436,9 @@ The state is the place's, not the type's, and one rule decided how it propagates The names were argued the same way. *Loose* was the first word for the moving state and the right image, but the spec already calls `'*` the loose form of an operator, and a second *loose* would fight the first on every page that used both. *Pinned* imports Rust's `Pin` along with its wrapper type and its `unsafe`, and *fixed* also means repaired and fixed-size. *Settled* reads in the hospitality register host and guest already live in — a guest can only visit a host that has settled — and *roaming* is its opposite in the same register, with no claim on it in programming worth defending. +> [!NOTE] +> Superseded: host and guest are now owner and reference, so *settled* no longer sits in a hospitality register; it keeps the image of a settler who has stopped travelling. See "[Owner and reference replace host and guest, and the store rule compares scopes](#owner-and-reference-replace-host-and-guest-and-the-store-rule-compares-scopes)". + Storage split along a line the containers drew for us. Fixed storage inherits: a struct's fields are all there from construction and never come or go, so they share their root's state. An array is the same shape, fixed in size and filled at construction, but it is a value type and holds no host to begin with. Dynamic storage does not inherit: a list's elements and a variant's payload are roaming even under a settled root, because they are places that appear and disappear while their owner lives. We considered settled lists, whose elements could be guested because they would never be moved out. That is a dynamic container that may grow but never shrink, and a container with that asymmetry has surprising semantics wherever it is used; a dynamic container should be strictly dynamic. So a settled list may itself be guested, and its elements never are. The float, which existed only so that a guested object could survive its place disappearing, has nothing left to rescue: nothing in a disappearing place is guested. Fields were the last question. We considered letting a field be declared roaming, so a settled object could hand a child off, and found it could not be checked where it is written — a struct's declaration does not know whether a given instance will settle — so a roaming field was dropped. What falls out instead needs no syntax: a field of a roaming host may be moved out, because the host is roaming, nothing can be watching it, and the gap is tracked in the host's declaration block exactly as a spent symbol is. A field of a settled host is never moved out, only overwritten. A swap that takes a settled object's child out while putting a replacement in would let a settled object hand children off; we left it for when a program needs it. @@ -485,3 +491,21 @@ Its elements needed no new rule. They are fixed storage, all present from constr The guest-source rule had excluded any path through `[]`, and `ArrayRef` needed a subscript that qualifies. Rather than mark subscripts, we let them follow the place they project ([`functions.md` §2.9](https://github.com/zane-lang/spec/blob/6e2a3ecfec2924f0c370b8d20752317409e5c590/spec/functions.md#29-subscripts-are-place-projections)). A subscript's body is itself a place expression, so whether `squad[2]` may mint a guest is whatever is true of the place the body names, followed through every subscript and field until it reaches an intrinsic projection. That is the same reasoning a verb returning `&T` already uses: whether `return someCall(guest)` type-checks depends on what `someCall` returns, not on the verb that forwards it. Exactly one intrinsic origin can be guested, the `ArrayRef` element under a settled root ([`memory.md` §2.8](https://github.com/zane-lang/spec/blob/6e2a3ecfec2924f0c370b8d20752317409e5c590/spec/memory.md#28-place-expressions-and-new--values)). The cost is a second fixed-size container that shares `Array`'s layout and differs in kind, so generic code over "a fixed run of `T`" chooses one, and an element can be replaced but never handed off. + +## Owner and reference replace host and guest, and the store rule compares scopes + +The settled-and-roaming chapters left the spec stating one rule over and over: nothing may point at a roaming host. Every time it was written, the vocabulary got in the way. "Guest" is a noun with no verb, and a rule about what may stand in a relationship needs the verb as often as the noun. So the spec had been forcing one, about forty times: a settled host "may be guested", a roaming one is "guested by nothing", "nothing guests it". "Nothing may reference roaming owners" says the same thing, and the version in our own vocabulary came out as "there may not be any guests to roaming hosts". A term that fights every sentence about its own central rule is costing more than a style complaint, because writers route around it with passives and generic words, and a generic word drifts away from the term it stands in for. + +Our first repair kept the noun and borrowed a verb. A guest *visits* a host, and the glossary was already reaching for that word ("a guest can only visit a host that has settled"). We rejected it quickly. "Visit" names the act of going to the object and using it, which is a dereference, while what the rules need is the standing relationship of holding an `&` at all. Every other verb of access (*use*, *reach*, *touch*) failed for the same reason. + +So we dropped "guest" and looked for a word that bends into both forms on its own. **`link`** came back first: plain, a noun and a verb, a relationship with no suggestion of ownership. [Naming the tether](#naming-the-tether) had set it aside once for colliding with linked lists and the linker. This time the objection was weight. A link reads as information, something you may follow or ignore, and an `&` is the opposite: an obligation the compiler holds the program to. The other candidates each lost on one count. **`pointer`** is the most accurate word for a repointable, storable address, and it arrives with null and pointer arithmetic attached. **`pin`** has the best image, since only something that holds still can be pinned, but the dependency spec already pins every version. **`address`** is literally true, but as a verb it mostly means *speak to*, and it is the machine-level word the source vocabulary had been split off to avoid. **`tie`**, **`bookmark`**, **`mention`** and **`attach`** were each workable, and each was lighter than the rule. That last failure named the requirement. The word should already carry the danger, so that a reader meeting it expects it can dangle and must not outlive what it names. Programmers learned exactly that about one word, and it was the word we had renamed away from. + +**`reference`** had been given up because of **reference type**. [Naming the tether](#naming-the-tether) left "ref" so the `#` axis could keep "reference" to itself, and our first instinct now was to rename the axis instead. *Identity type* and *object type* were the candidates, and we liked neither. What changed our mind was reading the two words together as a rule rather than a collision. An `&T` requires `T` to be a reference type, because only a reference type has an owner to point at ([`memory.md` §2.4](https://github.com/zane-lang/spec/blob/26e9e8d533e227929f7e8e505d54945de4f81677/spec/memory.md#24--is-a-reference-non-owning-storage)). A reference type is precisely the kind of type a reference may name, and the reason reference types need safety rules at all is that they can be referenced, which a value type cannot be. "A reference to a reference type" stopped reading as an echo and started reading as the definition. The one real hazard was smaller. A reference-type field owns its object, and a "reference field" would not, so one hyphen would carry the difference between owning and not owning. We closed that by naming an `&` slot by its sigil, an `&` field or an `&T` parameter, never a reference field ([the spec guide](../contributing/writing-spec-docs.md) §6.6). + +With the guest gone, **host** had nothing to pair with, and the reasons it had won were gone too. [The two-vocabulary chapter](#two-vocabularies-host-and-guest-above-anchor-and-tether) preferred it because "owner" stressed rights and destruction more than residence. Under settled and roaming, the role is ordinary ownership: one per object, governing its lifetime, handed on by a move. The plain word fits, and it gives the verb back for free ("`car` owns the engine", "nothing owns the result"). + +**Owner** was already taken, though. [The lifetimes story](lifetimes.md#the-owner-lifetime-replaces-the-same-root-rule) had used it for the lifetime the store rule compares: a symbol's declaring block, or a field's root symbol's. With hosts renamed, "the owner's owner" would have been a real phrase, naming two different links of one chain, the slot that holds an object and the block at the root of that slot. We tried "owning scope" first and rejected it for making a block an owner by implication. A block is not an owner of anything: nobody had ever called a block a host, and the new vocabulary should not start. What the store rule compares is always a block, so we called it what it is, a place's **scope** ([`lifetimes.md` §1.1](https://github.com/zane-lang/spec/blob/26e9e8d533e227929f7e8e505d54945de4f81677/spec/lifetimes.md#11-a-store-may-not-raise-a-value-above-what-it-names)). The spec's own prose had been calling the check "the scope comparison" all along. Now every sentence names one link: `car` owns the `Car`, and `car`'s scope is the block that declared it. *Owning edges* and *owning steps*, which describe the slot-to-object link, now match "owner" instead of competing with it. + +The runtime pair went too. *Anchor* and *tether* had lived on in the contributing guides as an internal register, though [anchors were removed](#guests-without-anchors) and nothing in the memory model carries either name. They are now history in this story and nowhere else. + +The cost is that the language now uses "reference" for two connected things, and keeping them apart is a writing rule rather than something the words do on their own. Readers also arrive with expectations from other languages. A C++ reference cannot be repointed, and a Zane reference can be repointed and stored. In Java a variable of reference type *is* a reference, while here the same declaration is an owner. *Settled* also loses the hospitality register it was chosen in, where a guest could only visit a host that had settled. It keeps its own image of a settler who has stopped travelling, which is the half of the argument that never depended on guests.