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 or GC 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 containing allocation plus projections that remain rooted in it.

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 ownership transfer at a proven final-use point.

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

Shared and exclusive aliases use non-lexical, control-flow-sensitive activity.


    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.

Expressions can construct new values from shared inputs: constructing a result doesn't implicitly copy the inputs. Template interpolation reads inputs and produces a new output string.


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

Exclusive access with ~


    update(~items)
    ~items.push(value) catch:
        assert(false, "unexpected push failure")
    ;
    

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.

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

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 reason about. The full language no-shadowing rule remains in docs/language-overview.md.

Common mistakes

Related reading