Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ The spec states *what* the language does; the **why** lives in a parallel set of
| [`stories/generics.md`](stories/generics.md) | [`spec/generics.md`](spec/generics.md) — the parameter model, the `<>`/`()` split, size-in-the-type, and the deferred features |
| [`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, and why `core` became a bundled implementation package |
| [`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 |
| [`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, and the root rule going back to "any parameter" once they left |
| [`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 |
| [`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 |
| [`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, and value-typed mutation closing the aliased-write gap |
| [`stories/error-handling.md`](stories/error-handling.md) | [`spec/error-handling.md`](spec/error-handling.md) — the two-doors model and why failure is control flow rather than a `Result` value, `resolve` as expression-substitution rather than assignment, typed abort paths and the deliberately-absent propagate operator, keeping abortability orthogonal to effects, and explicit path values through `Unit` |
Expand Down
2 changes: 1 addition & 1 deletion spec/adt.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ program Expr = Expr.op(Operation(Expr.intLit("3"), Expr.intLit("2"), Operator.ad
- **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.
- **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<T>` 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) whose host is in the same or a higher scope ([`lifetimes.md`](lifetimes.md) §1.1); an owning member, boxed or not, is subject to neither.
- **`&` 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).

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. Rehosting a reference-type node relocates every boxed descendant into the destination scope's dynamic region, so moving a tree costs time proportional to the tree rather than to its root (see [`memory.md`](memory.md) §3.5) — the same price a `List` already 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.

Expand Down
27 changes: 17 additions & 10 deletions spec/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ A write to `this` lands on the caller's object; how `this` reaches the caller di
- For a **value-type** subject, `this` is a **mutable borrow** of the caller's slot — the actual value, not a copy. The borrow makes the value mutable in place while preserving its value semantics. Because the borrow is scoped and non-escaping, `this` may be read and written but cannot be stored as an `&` or returned as one, since a value type is not `&`-rootable.
- For a **reference-type** subject, `this` is an implicit **guest**. The subject parameter is never a swallow position — a method does not consume the object it is called on — so bare `this T` here is a guest rather than the swallow it would be on an ordinary parameter, and **`&` is never written on `this`**: there is no second mode for the marker to select. The caller stays a full host either way.

A guest subject may be read, mutated, stored in an `&` field, or returned as `&T` ([`lifetimes.md`](lifetimes.md) §1.7), so a method that needs to keep its subject past the call needs no special declaration to do it.
A guest subject may be read, mutated, returned as `&T` ([`lifetimes.md`](lifetimes.md) §1.7), or used as the destination of an `&` store ([`lifetimes.md`](lifetimes.md) §1.1), so a method that needs to keep its subject past the call needs no special declaration to do it.

```zane
Unit setScale(this Node, scale Float) mut { // reference subject: the implicit guest
Expand Down Expand Up @@ -113,7 +113,7 @@ Explicit parameters other than `this` are read-only: they cannot be assigned or
### 2.8 Swallow and guest method parameters
A reference-type method parameter selects one of two passing modes ([`memory.md`](memory.md) §2.9):

- A parameter declared as `&T` is a **guest**: the caller supplies a guest source under [`memory.md`](memory.md) §2.8, which a bare symbol satisfies, and the callee may read it, mutate it, store it into an `&` field, or return it.
- A parameter declared as `&T` is a **guest**: the caller supplies a guest source under [`memory.md`](memory.md) §2.8, which a bare symbol satisfies, and the callee may read it, mutate 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` **swallows** its argument — it takes the value by hosting access, which the value's call-site scope keeps ([`lifetimes.md`](lifetimes.md) §1.5).

A swallowed parameter may not be bound into `&` storage, because it is hosted at the call site while an `&` field may outlive the call. A value-type parameter is always a read-only borrow. To pass a reference object without giving up hosting, use `&T`.
Expand All @@ -124,17 +124,17 @@ type Car = #struct {
_value Int;
}

// `&` parameter: may be stored into an `&` field
Unit setEngine(this Car, engine &Engine) mut {
this.engine = engine // legal
return Unit()
}

// `&` parameter used only to read
Int calculate(this Car, engine &Engine) {
return this._value + engine.speed // legal: reading through the guest
}

// `&` parameter stored into an `&` field: recorded in the signature, checked per call
Unit setEngine(this Car, engine &Engine) mut {
this.engine = engine // legal here; each call compares the two argument paths
return Unit()
}

// plain reference-type parameter swallows; the swallowed value is hosted at the call site
Unit setEngineWrong(this Car, engine Engine) mut {
this.engine = engine // ILLEGAL: cannot store a swallowed host into an `&` field
Expand All @@ -149,11 +149,18 @@ engine Engine()
garage Garage()

car:calculate(engine) // legal: a bare symbol is a guest source
car!setEngine(engine) // legal: same, and the guest is stored in an `&` field
car!setEngine(garage.spare) // legal: a field access is a guest source
car!setEngine(engine) // legal: 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()) // ILLEGAL: a temporary is not a place expression
{
spare Engine()
car!setEngine(spare) // ILLEGAL: this block does not outlive car's
}
```

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.

### 2.9 Subscripts are place projections
Subscripts are package-scope declarations with the subject first:

Expand Down
14 changes: 12 additions & 2 deletions spec/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ This file gives short, reusable names to concepts that appear across multiple sp
- **Canonical home:** [`memory.md`](memory.md) §2.1

### 3.33 guest
- **Meaning:** The source-facing `&T`: access to a hosted reference-type object without storing that object or controlling its lifetime. A guest may be repointed, copied when assigned or passed, stored in an `&` field, or returned as `&T`, but it cannot outlive its host. It may be minted from any place but a `[]` expression (§3.36), and it names the object hosted there at that moment, travelling with that object if it is later moved. Internally, a guest is represented by a tether (§3.24) that resolves through an anchor cell (§3.23).
- **Meaning:** The source-facing `&T`: access to a hosted reference-type object 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. Every store of a guest, and every later store of a value that carries one (§3.42), compares owners (§3.43): what the guest names must have an owner that outlives the owner of the place holding it ([`lifetimes.md`](lifetimes.md) §1.1). It may be minted from any place but a `[]` expression (§3.36), and it names the object hosted there at that moment, travelling with that object if it is later moved. Internally, a guest is represented by a tether (§3.24) that resolves through an anchor cell (§3.23).
- **Why this name:** A guest may use what a host provides without owning it, and the guest's stay cannot outlast the host. The pair names the source relationship without exposing its runtime mechanism.
- **Canonical home:** [`memory.md`](memory.md) §2.4

Expand All @@ -245,7 +245,7 @@ This file gives short, reusable names to concepts that appear across multiple sp
- **Canonical home:** [`memory.md`](memory.md) §2.8

### 3.37 passing mode
- **Meaning:** Which of two ways a reference-type argument reaches a callee, fixed entirely by the parameter's surface form: `T` **swallows** it (hosting access; the caller downgrades to a guest), `&T` takes a **guest** (readable, mutable, storable, and returnable; requires a guest source, which a bare symbol satisfies). The subject parameter (§3.38) has no such choice — it is always a guest — so `&` is never written on `this`. Two overloads may not differ only by the mode at one position.
- **Meaning:** Which of two ways a reference-type argument reaches a callee, fixed entirely by the parameter's surface form: `T` **swallows** it (hosting access; the caller downgrades to a guest), `&T` takes a **guest** (readable, mutable, returnable, and storable, with a stored guest's resting place recorded in the signature and checked at each call; requires a guest source, which a bare symbol satisfies). The subject parameter (§3.38) has no such choice — it is always a guest — so `&` is never written on `this`. Two overloads may not differ only by the mode at one position.
- **Why this name:** "Mode" names a choice about *how* the same argument travels rather than *what* it is — the type is unchanged in both, and only the caller's obligations and resulting state differ.
- **Canonical home:** [`memory.md`](memory.md) §2.9

Expand All @@ -269,6 +269,16 @@ This file gives short, reusable names to concepts that appear across multiple sp
- **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
- **Meaning:** An `&` reachable from a value's type by following **owning** edges — the same graph the boxed-member rule reads (§3.39). The walk finds an `&` member and stops there: the `&` itself is a carried guest, and a type's own `&` field is the shortest case, found after no edges at all. What the walk does not do is continue *through* the `&` into whatever it names, because that object is hosted elsewhere and travels separately. The walk reads the declared type — for a `#variant`, every case, since which case is live is the flow-sensitive fact the declaration-block rule refuses to track — and so decides only whether a value *may* carry a guest; what one names is read from the value's construction. Every store of a value re-compares the owners (§3.43) of the hosts its carried guests name, which is why a check made where the value was first written does not have to survive the value moving. A guest naming a host **inside** the value is satisfied at every destination, because that host travels with it. A value carrying no guest skips this re-check only; its own host is still compared at every store and move, as for any value.
- **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.
- **Canonical home:** [`lifetimes.md`](lifetimes.md) §1.10

### 3.43 owner
- **Meaning:** The lifetime a place belongs to, and the only thing the store rule compares. A **symbol** 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; a **parameter** (`this` included) and a constructor's `init{ }` have no owner in the body at all — each stands for a path in the caller's frame, so a store reaching one is settled at the call site. A path that steps *through* an `&` has left the tree its root names: what lies beyond belongs to a tree the path never mentions, so it has **no** owner, may be read freely, and may never be a store destination — `main.peer.io` on an `&` field `peer` is not an owned place. A block outlives every block nested within it, and a block is one lifetime rather than a sequence: everything it owns dies when it drains, with nothing to observe between. Hosts inside a stored value travel with it and take the destination's owner.
- **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.
- **Canonical home:** [`lifetimes.md`](lifetimes.md) §1.1

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
TheLazyCat00 marked this conversation as resolved.
---

## 4. Packages, Operators, and Versioning
Expand Down
Loading