Memory management / Affine ownership and drops
Cleanup responsibility is affine: it may move or be discharged, but it never duplicates. The compiler transfers it only where static analysis proves no later source use exists, and lowering consumes a validated memory plan rather than advisory drop sites.
Moth ownership is a runtime cleanup discipline over statically validated topology. It isn't a separate source type system.
Cleanup responsibility is affine: it may move or be discharged, but it never duplicates. That single property is what makes deterministic cleanup safe without counting ordinary aliases.
Borrow and lifetime analysis prove transfer eligibility and topology legality. Ownership lowering only realises already-validated facts. The compiler-owned memory planner produces concrete responsibility and retained-edge obligation transitions plus cleanup and destruction plans. Backend lowering consumes the ValidatedMemoryPlan and encodes its planned transitions and cleanup decisions rather than reconstructing them from borrow-checker advisory cleanup sites.
The compiler may transfer cleanup responsibility only where static analysis proves that doing so can't invalidate a later source use. If that proof is unavailable, the operation remains a borrow and the allocation stays under its region owner.
TypeId.Bit 0 of a full-control allocation-family handle records affine cleanup responsibility. It does not prove uniqueness, identify the final observer, establish lifetime legality or create dynamic shared ownership.
borrowed
The runtime path must not destroy the value.
owned
The runtime path carries cleanup or safe-transfer responsibility.Cleanup responsibility and physical representation are independent dimensions of a full-control handle. Bit 0 is per-handle affine cleanup responsibility. Bit 1 describes the target allocation family's physical representation. Neither bit determines the other.
bit 0: affine cleanup responsibility
bit 1: REC representation00: uncounted borrowed
01: uncounted affine-owned
10: REC-managed borrowed or counted persistent edge
11: REC-managed affine ownerThe tags belong to an allocation-family handle, so a projection must retain or recover the family base. Scalars and target-native immediate values do not use this handle ABI, and GC-native backends may erase the tags while preserving the same semantic analysis.
Only retention-sensitive and cleanup-sensitive operations need to inspect bit 1. The compiler-owned memory planner decides REC selection, concrete obligation transitions and destruction plans. Backend lowering encodes those decisions. The retained-edge-counting page documents the REC representation contract.
Contrast these distinct concepts:
Semantic lifetime ownership stays static even when cleanup responsibility moves at runtime. The bit does not imply individual releasability when the value belongs to an inferred-region-owned or declared-region-owned allocation family.
This section is the terminology authority for these four cleanup terms:
A discharged obligation is gone from this path. It does not by itself mean that the allocation family is destroyed. The selected physical strategy determines what happens after the obligation is discharged:
uncounted individually releasable family:
discharge may destroy immediately
REC family:
discharge removes the affine-root obligation
destroy only when the total count reaches zero
inferred-region family:
the individual handle does not own region reclamation
declared-region family:
no individual discharge destroys declared-region-owned storage
declared-region exit bulk-reclaims itThese terms are not synonyms.
Assignments and calls can be optional transfer points. Analysis proves eligibility. Lowering realises validated transfer facts. There is no move keyword.
When transfer is realised:
Immutable and mutable parameters may both receive inferred cleanup responsibility at a proven final-use call site. Parameter mutability controls access; ownership metadata controls cleanup responsibility. An immutable parameter receiving ownership remains read-only. A mutable parameter receiving ownership may mutate according to the source contract.
Closed external boundary profiles override this general rule. WIT value-only calls are non-consuming. Restricted host-value crossings are non-consuming, and mutable opaque-handle access does not transfer Moth storage through the ordinary Moth ownership ABI.
Ownership-aware lowering uses one calling convention rather than separate source-visible borrowed and owned function variants. Because responsibility and representation are independent handle dimensions, one convention covers every combination of the two bits.
Heap-managed or handle-based values that participate in ownership lowering carry ownership metadata with the runtime value. Scalar values may continue to use target-native representations.
ABI behaviour:
The unified ABI keeps dispatch static without generating separate borrowed and owned function families. It limits ownership-driven monomorphisation and binary-size growth, especially for Wasm, while preserving the same semantic ABI when local specialisation removes a known check.
An individual discharge is legal only when:
When an owning inferred or declared region ends, bulk reclaim handles region-owned storage instead of individual discharge.
discharge_if_owned is the conceptual operation at a possible individual-cleanup point. It checks ownership metadata and, when responsibility is owned, discharges that affine obligation according to the concrete transitions in ValidatedMemoryPlan. A discharge may destroy an uncounted individually releasable family immediately. For an REC family it removes the affine-root obligation and destroys the family only when the total count reaches zero. Inferred-region and declared-region families remain under their owning region's reclamation rules.
The current Wasm scaffolding instruction is spelled DropIfOwned; this page names it only when describing migration debt to discharge_if_owned, not as a second conceptual operation.
Possible cleanup points include:
break and other reachable loop-exit pathsA non-exiting loop needs no cleanup point. A loop that can exit must account for ownership on each reachable break, return, error-return or region-exit path.
A control-flow join determines whether a later exit may own. The join itself is not a cleanup point and is not automatically a discharge site.
Deterministic cleanup is enabled where analysis supports it. Where individual discharge cannot be placed, the allocation remains under its inferred or declared region owner and is bulk reclaimed at that owner's validated exit. Conservative retention is the acceptable outcome; falling back to a tracing collector in a capable release build is not.
Aggregates may contain shared and independently owned children.
Accepted effect categories:
MayConsume
NeverConsumes
AlwaysConsumesThese are analysis/lowering facts only, not required exact Rust enum names and not source-visible signature categories.
MayConsume retains conditional ownership handling.NeverConsumes can omit conditional discharge handling.AlwaysConsumes may remove ownership-condition branches only after call-path proof.Retained Edge Counting is permitted as a narrow physical representation for statically legal topology with runtime-many persistent retained edges. The memory planner selects it for eligible families, and backend lowering encodes the selected layout and planned transitions. Source-visible RC, explicit retain/release APIs, weak ownership, finalizers or dynamic shared-ownership semantics remain outside the language design.
Compatible extension points within the accepted memory model:
These must preserve source-language semantics and the unified ownership contract. A missing physical strategy after successful topology validation is a CompilerError, not a silent collector fallback.
~ as ownership transfer.ValidatedMemoryPlan.