Memory management / Access and aliasing

Moth is reference-semantic by default, copy-explicit and move-inferred. It omits explicit reference types and lifetime syntax, not references themselves.

Existing values are shared by default. Copies are explicit. ~ requests exclusive access to a mutable place.

Access and aliasing

Design contract

Moth is reference-semantic by default, copy-explicit and move-inferred. It omits explicit reference types and lifetime syntax, not references themselves.

Existing values use shared, read-only access by default. Independent copies require explicit copy. Mutation requires explicit exclusive access to an existing mutable place.

Reference relationships are real source semantics. Moth does not expose reference type constructors or lifetime syntax.

These are source-semantic rules. Backends may use different scalar, handle, pointer, region, counted or garbage-collected representations as long as they preserve the same observable behaviour.

Main compiler paths

This is a navigation map, not a status declaration.

Vocabulary

Term

Definition

Value

A Moth language value. Not necessarily heap allocated.

Place

Existing storage that can be read or mutated.

Shared access / shared alias

Read-only source-semantic access to existing storage.

Mutable alias / exclusive alias

Write-through mutation-capable access to existing storage.

Retained reference

A stored reference that survives the immediate operation.

Independent copy

A new value graph created by explicit copy with no mutable sharing of the source graph.

Allocation family

The semantic allocation root that must be kept alive as one unit, plus every projection that remains rooted in it. Cleanup targets the family root, never an interior projection.

Alias binding

A binding that observes existing storage rather than creating an independent copy.

Exclusive access

Mutation-capable access to one existing place.

Move / inferred transfer

Compiler-inferred optional transfer of affine cleanup responsibility at a proven final-use point.

Result provenance

The static fact describing where a result's storage came from: a fresh root, an alias or projection of a parameter, a detached stored value, an alias of another result, or an independent graph.

Cleanup responsibility

The runtime fact describing which path may perform individual cleanup. It is affine: it may move or be discharged, but it never duplicates.

Fresh value

A newly produced literal, template, constructor result or computed aggregate.

Shared access

Reads of existing values are shared. Ordinary immutable arguments are shared. Multiple shared reads may coexist. Shared access is read-only, doesn't imply ownership and can prevent mutation while live. Scalar values may be represented directly by a backend.

Shared access is a source-semantic relationship. It does not require every scalar to be heap allocated or represented by a pointer.

first = load_items()
second = first

inspect(first)
inspect(second)

second doesn't imply a clone. It's a shared read-only alias of the same source value.

For x = y:

Alias activity and last potential use

Alias activity is path-sensitive and control-flow-sensitive. Shared aliases, and exclusive aliases that have a reachable holder use, use non-lexical activity. Whether an issued but never-used exclusive capability may be elided remains an explicit experimental question; the reference baseline keeps that capability conservative until canonical semantics adopt the stronger rule.

items ~= {"a", "b"}
shared = items
inspect(shared)

change(~items)   -- valid: shared has no later potential use
items ~= {"a", "b"}
shared = items

change(~items)   -- invalid: shared is used again below
inspect(shared)

Lexical scope does not define alias activity. See Lifetime regions and escape validation when a retained alias escapes its original binding or region.

Alias bindings

alias = source

An alias binding normally observes the same source-semantic storage or root. It doesn't create an independent value. It can participate in mutation-conflict analysis and optional transfer reasoning. An ownership-aware backend may optimise an alias into storage transfer when no later source use exists, but it must still behave as though source aliasing rules were respected.

Borrow-checker slot and alias states are analysis concepts, not source-visible runtime types.

Mutable aliases versus mutable slots

alias ~= source

alias ~= source, or an equivalent typed mutable declaration from an existing place, creates an exclusive mutable alias.

A mutable declaration initialized from a fresh value creates an independent mutable slot:

count ~= 0
count = 1

~ on the declaration marks the binding as mutation-capable. Reassignment still uses =. Mutable binding syntax is different from call-site exclusive access. Mutability is an access property, not separate semantic type identity.

Explicit copies

snapshot = copy source

copy place performs a semantic deep copy of the complete copyable runtime value graph reachable from the place.

-- conceptual: non-copyable external handle inside a graph
-- copy payload   -- invalid when payload contains a non-copyable resource

Exclusive access with ~

items ~{String} = {"Priya"}

update(~items)
~items.push("Rob")

The source form is ~place. The place must already exist, and the underlying binding must permit mutation. Exactly one exclusive access may be active at a time. Shared access conflicts with active exclusive mutation where their roots overlap. ~ isn't an explicit move. The compiler may later realise an eligible operation as ownership transfer.

Fresh values and hidden locals

update({"fresh"})
update(Vector2(x = 1.0, y = 2.0))
update([: generated])

These forms are valid for ordinary mutable parameters. Source ~ on a fresh value isn't:

update(~{"fresh"})   -- invalid
update(~Vector2(x = 1.0, y = 2.0))   -- invalid

Fresh values don't need source ~. A mutable parameter may accept them. HIR materialises a hidden local when borrow analysis needs a place. The hidden local isn't source-visible lifetime syntax. There is no special temporary-reference node category after materialisation.

Fresh-rvalue materialisation applies only to ordinary mutable parameters. It does not make temporaries valid mutable receivers.

Calls and receiver methods

Collections, maps and stored values

Existing values stored in aggregates retain shared reference semantics by default. copy creates independent storage. At a proven final use, the compiler may realise the same source operation as an ownership transfer without changing source meaning.

This applies to structs, choices, collections, maps, tuples, templates and other aggregates.

Last-use analysis is central to the whole memory model, not a peripheral optimisation. It is what lets the compiler transfer affine cleanup responsibility, end inferred regions early and avoid counting most allocations. When a final use cannot be proven, the operation stays a borrow and the program remains valid.

Topology questions about retained edges, escapes and region owners belong to Lifetime regions and escape validation.

Static provenance vs runtime responsibility

Two distinct facts travel with a value. Do not collapse them.

Static result provenance answers where storage came from and which lifetime constraints apply to it. It is a compile-time summary fact: fresh root, alias of one or more parameters, projection of a parameter, detached stored result, alias of another result or independent result graph, together with any retained-parameter and outlives constraints. Declared-region extraction or adoption and interior projection detachment are separate topology questions. Provenance never enters TypeId and is never source syntax.

Runtime cleanup responsibility answers who may perform individual cleanup on the current path. It is affine and may move through calls, returns and aggregate insertion, but it never duplicates.

A value can be a projection of a parameter (provenance) while the caller retains cleanup responsibility (runtime state). A fresh result root may carry cleanup responsibility to the caller, or be constructed directly into a caller-selected hidden destination. The hidden destination is not source syntax, not a lifetime parameter and not part of any signature.

Semantic lifetime ownership stays static even when cleanup responsibility moves at runtime.

Allocation families and projections

An allocation family is the unit that lifetime analysis and cleanup operate on. A field access or element access produces a projection that remains rooted in its containing family, so retaining a projection retains the whole family.

Field-sensitive allocation splitting may later prove that a projected field is an independent family with its own lifetime owner. Until that analysis runs and succeeds, treat every projection as rooted in its parent. A proven final use transfers the entire allocation family rather than detaching one child.

No source-level reference, move or lifetime syntax

Reference relationships still exist as source semantics. Moth has no source-level:

These are permanent design boundaries, not merely unimplemented features.

No shadowing

One visible name maps to one binding. This keeps alias roots, future-use reasoning and diagnostics easier to follow. The full language no-shadowing rule lives in docs/src/docs/bindings/shadowing.mtf.

Common mistakes

Related reading