Ownership and Move Semantics
Status — accepted. Establishes the foundational ownership model for Metel values. Required by RFC-0063 (Allocator Handles) and all downstream allocator RFCs, which depend on affine ownership as a given.
Refreshed 2026-07-24, before integration — this RFC had gone stale in place. Accepted 2026-06-28 and never touched since, it missed every subsequent corpus-wide change because nothing was working on it. Four kinds of drift, all corrected, none semantic:
was now why "the Copytrait""the Copyaspect"Metel has aspects; "trait" is not the language's word extend Point: Copy {}extend Point: Copy;RFC-0098 ( 4-implemented), plus v0.10.0's bodyless form@[r] T@a TRFC-0063's allocator syntax "the region system", RFC-0063 "(Region Handles)" the allocator system the region cluster is 6-refused(RFC-0025/0028/0056/0069/0087); RFC-0063/0066/0068 were renamed 2026-07-10The last row is the substantive one. §8 described this RFC's interaction with a subsystem that has since been refused outright and replaced. The claims themselves survive — affine ownership is what makes allocator lifetimes sound, exactly as it made region lifetimes sound — but they were stated about a system that no longer exists.
Worth recording as a process observation, not just a diff. RFC-0063, RFC-0066 and RFC-0068 were all renamed in the 2026-07-10 sweep. RFC-0071 sits in the same cluster and was missed, because it was
2-acceptedwith no open work against it — the one state in the lifecycle where nobody has a reason to open the file. It is the most-depended-on document in the corpus and it spent a month describing a refused subsystem.
Cross-checked 2026-07-24 against the records cluster, which did not exist when this RFC was accepted. Four interactions; three are clean and the fourth is a real gap.
§7 versus RFC-0117 (Row Narrowing) — consistent, and worth saying so. §7 states a partially-moved value "may not be used as a whole"; RFC-0117 states that a partially-moved record narrows to a first-class value of a narrower type. These read as contradictory and are not: a struct has no row to narrow to, so §7's rule is what remains when row machinery is absent. The tier system is doing the work.
§7's
Dropban versus RFC-0116 §3 — clean by construction. §7 forbids partially moving aDroptype. RFC-0116 §3 forbids customDropon a record entirely. So a record is neverDrop, and RFC-0117's narrowing can never collide with §7's ban.§7 versus RFC-0114 (Construct) — no conflict, but §7 is silent where RFC-0114 speaks. §7 covers moving out; it says nothing about reassembly, because a partially-moved struct simply stays unusable. RFC-0114 §3 governs the inverse for records (completing a row fires
construct). Neither contradicts the other; the silence is the design.§2 versus RFC-0116 §3 — a real gap: no anonymous record can ever be
Copy. §2 makesCopyan opt-in aspect declared withextend T: Copy;. RFC-0096's auto-impl list is a closed set of exactly three —Send,Sync,Linear— andCopyis not in it, so it must be declared. RFC-0116 §3 bans non-local aspect impls for records, andCopyis standard-library. Therefore every record is affine and must be moved, including{ x: i64, y: i64 }, which is precisely the shape a reader would expect to be freely copyable.This is the same class of problem as records not being
Display, and it bites harder in one place: RFC-0121's width-subtyping rule requires every silently-dropped field to beCopy, so a dropped field that is itself a record could never satisfy it. Both need RFC-0123's field-wise constraints. Recorded in RFC-0116 and RFC-0123 as well; not a blocker for this RFC, which is correct as written —Copybeing declared rather than derived is deliberate.
Status — integrated 2026-07-24, targeting v0.12.0. §1–§7 are merged into
public/reference/spec/as a new page,spec/ownership.md— the spec had no home for ownership at all, andCopy/Dropappeared in it only as bounds inside coherence examples, never defined. Added to the Contents table between Type System and Declarations. Four one-line availability markers.Integration found two contradictions in the spec's own front matter, both from the language having moved on without it:
spec.mdlisted a core design principle reading "Safe memory by default — reference counting, no ownership semantics required." That is the direct negation of this RFC. It described v0.7-era Metel accurately and has been false-in-intent since this RFC was accepted on 2026-06-28. Replaced with affine ownership, carrying a marker stating that the interpreter still copies everything until v0.12.0.spec.md's frontmatter saidversion: v0.7.0, four releases stale. Now v0.11.0. The two are related: that block had not been revisited since v0.7, which is why the principle survived.Sibling cross-checks are in §9a and were done before this pass — three clean interactions with the records cluster and one real gap (no record can ever be
Copy). Nothing in the spec merge changed them.What the page deliberately does not claim. Its closing section states that ownership answers "how many owners" and
Copyanswers "may this be duplicated", and that neither answers "what is borrowed right now" — with an explicit note that nothing here prevents two&var Tto the same place. That is RFC-0122's rule and is not in this release; saying so on the page is better than letting a reader infer exclusivity from the word "exclusive" two pages away.
Status — integrated (2026-07-24). New spec page ownership.md covering sections 1-7; four availability markers; Contents table updated. Found and fixed two spec contradictions: the 'reference counting, no ownership semantics required' design principle, and a v0.7.0 version stamp. Tracked as #290-#262; #579 (move checking) is canonical.
Revised 2026-08-27:
Drop::droptakesself: &var Self, notselfby value. §3's declaration and worked example updated fromfun drop(self)tofun drop(&var self). No behavior change for a shipped program — destructor invocation itself is still unimplemented (#261).
Summary
Metel values are affine by default: a non-Copy value has exactly one owner at any
point in time. Moving a value transfers ownership to a new binding; the source becomes
invalid. This RFC specifies:
- move semantics as the default for all struct and enum values;
Copyas an opt-in aspect for types that may be bitwise duplicated;Dropas an opt-in aspect for types with destructor logic;- the mutual exclusion of
CopyandDrop; - drop order within a scope;
- explicit drop and partial moves.
Motivation
Affine ownership is the foundation of Metel's memory safety model. The allocator system
(RFC-0063 and downstream) relies on allocator-tagged pointers being affine — if @a T could
be copied freely, the entire lifetime and disjointness analysis would be unsound. The borrow
checker's single-owner invariant, the T: !Drop constraint on scoped move-out (RFC-0066),
and the drop ordering that makes struct-owned arenas safe (RFC-0068) all assume that values
move rather than copy by default.
This RFC makes that assumption explicit and normative.
1. Values move by default
When a value of a non-Copy type is assigned, passed as an argument, or returned, it is
moved: ownership transfers from the source to the destination. After a move, the source
binding is invalid and may not be used.
let x := Node { val = 1 };
let y := x; // x is moved into y; x is now invalid
process(y); // y is moved into process; y is now invalid
The compiler enforces this statically. A use of an invalidated binding is a compile error:
error: use of moved value `x`
--> ...
| let y = x; // x moved here
| …
| let z = x; // error: x is no longer valid
Move semantics apply to every non-Copy value by default. Primitive types and types
implementing Copy are excluded (§2).
(Wording widened 2026-07-24. This previously read "all struct and enum values", which was exhaustive when written and no longer is — it excludes records, tuples and arrays by omission. See §9a.)
2. The Copy aspect
A type implementing Copy is bitwise-copyable: whenever it appears in a value
position, a copy of its bits is made and the original remains valid. No ownership transfer
occurs.
let x: i64 := 42;
let y := x; // copy — x is still valid
let z := x; // copy again — x is still valid
Copy is opt-in. The following are Copy by default:
- Primitive numeric types (
i8–i64,u8–u64,f32,f64) boolean,Char- Fixed-size arrays whose element type is
Copy - Tuples whose element types are all
Copy
Corrected 2026-07-25: this list said
boolandchar. Metel's primitives are namedbooleanandChar; the earlier spelling names two types that do not exist.
Where each of these lives is not a free choice — measured against the implementation on 2026-07-25:
- The primitives go in
stdlib/, oneextend i64: Copy;per type. Verified: a primitive is a validextendtarget today.- Fixed-size arrays and tuples must be built into the checker for now, and should carry a comment saying so.
extend (A, B): Copyon a concrete tuple raises an internal error (#581), and the generic formextend<A: Copy, B: Copy> (A, B): Copy;is accepted but never satisfies — a silent no-op, which is worse.[T; N]would need a const-generic arity that does not exist; only literal arities parse. The migration out of the typechecker is tracked as #263, which records what must exist first: #581 plus RFC-0061 §6's deferred per-arity decision for tuples, and a const-generics RFC (none exists; RFC-0053 defers to it) plus RFC-0124 for arrays.- Do not write
extend<T: Copy> T[]: Copy;. It works today and is wrong:T[]is the dynamic,Rc-backed array, so a "copy" would duplicate the handle and silently alias one buffer. Only fixed-size arrays areCopy, exactly as this section says. (Noted because the one array form stdlib can express is the one that must not be written.)
Structs and enums are not Copy unless explicitly declared. A type may implement Copy
only if all its fields (for structs) or all payload types (for enum variants) are Copy;
the compiler enforces this structurally:
struct Point { x: f64, y: f64 }
extend Point: Copy; // valid — f64 is Copy
struct Node { val: i64, next: @a Node }
extend Node: Copy; // compile error — @a Node is not Copy
3. The Drop aspect
A type implementing Drop declares destructor logic that runs when its last owner is
dropped — either by going out of scope or by an explicit drop call (§6).
struct Handle { fd: u64 }
extend Handle: Drop {
fun drop(&var self) {
close_fd(self.fd);
}
}
{
let h = Handle { fd = open("file.txt") };
use_handle(&h);
} // h goes out of scope; Handle::drop runs automatically
Drop is opt-in. Types without a Drop impl are reclaimed by recursively dropping their
fields, with no user-defined logic.
4. Copy and Drop are mutually exclusive
A type may not implement both Copy and Drop. The combination is unsound: if a Copy
type could be duplicated freely, the destructor would run once per copy, potentially
releasing the same resource multiple times.
extend Handle: Copy; // compile error — Handle implements Drop
The negative bound T: !Drop (RFC-0066) is satisfied by any type with no Drop impl.
All Copy types satisfy T: !Drop by this mutual exclusion rule — Copy implies !Drop.
5. Drop order
Coverage: untestable (see frontmatter). Destructor invocation is not implemented yet.
Within a scope, values are dropped in reverse declaration order — the last-declared value is dropped first:
{
let a = A::new(); // dropped third
let b = B::new(); // dropped second
let c = C::new(); // dropped first
} // c drops, then b, then a
Struct fields are dropped in declaration order — first field first. This is symmetric with construction order and allows later fields to safely depend on earlier ones at init time without requiring reverse cleanup logic.
struct Conn {
socket: Socket, // dropped first
buffer: Buffer, // dropped second
}
For structs that own an allocator (struct Parser(@a: BumpAlloc), RFC-0068), the struct's
fields are dropped before the owned arena is freed. This ensures that any @a T pointers
stored as fields
are unreachable before the bulk free, preventing use-after-free at the drop site.
6. Explicit drop
Coverage: untestable (see frontmatter). Explicit dropping depends on unimplemented destructor invocation.
A value may be dropped before the end of its scope with the free function drop:
let handle := Handle { fd = open("file.txt") };
use_handle(&handle);
drop(handle); // destructor runs here; handle is invalid from this point
drop takes ownership of its argument. The compiler treats the binding as moved-out after
the call; any subsequent use is a compile error.
7. Partial moves
Moving out of a struct field leaves the containing value partially moved. A partially moved value may not be used as a whole; only the remaining un-moved fields may be accessed:
let p = Pair { a = String { … }, b = 42i64 };
let s = p.a; // p.a moved out; p is partially moved
let n = p.b; // p.b moved out; p is now fully consumed
// p itself cannot be used as a whole at any point after the first partial move
A struct implementing Drop may not be partially moved — the destructor requires access
to the complete value. The compiler rejects partial moves of Drop types:
let h := Handle { fd = open("file.txt"), tag = 1u64 };
let fd := h.fd; // compile error — Handle implements Drop; partial move not allowed
Planned to be relaxed (RFC-0137,
2-acceptedas of 2026-08-27 — briefly2-acceptedon 2026-08-25 too, reverted the same day, then re-accepted; not yet integrated or implemented). RFC-0137 (Nominal Types as Branded Rows) supersedes this unconditional ban in design with row-boundedDropdispatch — a struct's own row makes it possible to check whether a given residual still has every field a specificDropimpl actually reads, rather than rejecting every partial move outright. This is a design supersession only: the rule above is what--move-checkenforces today, and continues to enforce unconditionally, until RFC-0137's own row-bounded mechanism is actually built.
7.1 Moving out of a reference
Added 2026-08-01 (metel-core#602). A reference — &T or &var T — only ever grants
access to the value it points at; it never grants ownership of it. Consequently, a
non-Copy value reached through any reference cannot be moved out of it. This is the
reference analogue of §7's own rule — partial moves are banned because a Drop type needs
its whole value intact; moving out of a reference is banned because no type's value can
be given up when this scope only borrows it.
aspect Consume { fun eat(self) -> String; }
fun main() {
let b := Handle { fd = open("file.txt") };
let r := &b;
let taken := r.eat(); // compile error — `eat` takes `self` by value,
// but `r` only borrows `b`
}
Enforced scope, as of #602 and #648. --move-check enforces this rule at the
method-receiver position — calling a by-value self method through a reference — and,
as of #648, at every other position a value can be moved from: general assignment
(let x: B = *r;), by-value argument passing (f(*r)), and a plain field read through a
reference receiver with no explicit * at all (self.field inside a &self method,
r.field for any reference-typed r). All of these share one mechanism
(illegal_move_kind's per-projection walk, banning the step that reads across a
reference boundary), so the rule applies uniformly regardless of how the reference is
reached: the receiver's own binding (r.eat()), a field or tuple element of reference
type (pair.0.eat()), an explicit dereference ((*r).eat()), a type parameter
instantiated to a reference (fun twice<T: Consume>(x: &T)), or an interior
reference-typed field crossed via auto-deref (outer.inner.payload.eat() where
inner: &Middle). &self and &var self methods are unaffected when they only ever
read through the receiver — those already take it by reference, so no ownership
transfer is being asked for. A Copy pointee is also unaffected — reading a copy back
out through a reference is exactly what Copy permits (RFC-0067a §3a).
A related gap, deliberately not covered by #648, is now closed separately:
RFC-0067a's own §3a T: Copy gate on read-copy positions — let x: T = r;, binding a
reference directly to a differently-typed local with no field/index/deref projection at
all — was structurally distinct from everything #648 fixed (illegal_move_kind's
per-projection walk never runs for a zero-projection place, so it couldn't reach this
case) and stayed unenforced after #648 landed. Fixed by
metel-core#649, as a hard,
always-on type error (T0024) in the typechecker itself rather than under
--move-check — see RFC-0067a §3a.
8. Interaction with the allocator system
Allocator-tagged pointers (@a T) are non-Copy by construction — they carry an allocation
that must have a single owner at all times. Affine ownership is the mechanism that makes
allocator lifetime guarantees sound:
- Because
@a Tis affine, any allocator-tagged value always has exactly one live owner. This is what allows the interpreter's uniform allocator to provide deterministic drop semantics equivalent to the compiled allocator system. - The
T: !Dropbound in RFC-0066 §2.2 requires the definitions ofDropand the negative bound mechanism established in §3–4 of this RFC. - The drop ordering in §5 directly determines the order in which arena-allocated fields
become unreachable before
drop(r)reclaims the arena's backing memory.
9c. Implementation tracking
Coverage: untestable (see frontmatter). This is issue-tracking content, not fixture-observable behavior.
Filed 2026-07-24, against milestone v0.12.0. Four issues rather than the one per RFC
AGENTS.md prescribes — a deliberate exception, because this RFC is seven sections of
essentially unbuilt work with a real dependency order between the pieces:
| issue | sections | depends on |
|---|---|---|
#578 — Copy and Drop aspects | §2, §3, §4, §9 q3 | — |
| #579 — move checking | §1 | #578 |
| #261 — drop order and explicit drop | §5, §6 | #578, #579 |
| #262 — partial moves | §7, §9a | #579 |
§9b's place-abstraction requirement is stated in #579, which is where it constrains the design. §9a's rules for tuples, arrays and enum payloads are in #262. #262 is also the one v0.13.0 depends on — RFC-0117 is built on field-granularity partial-move tracking, so descoping it pushes RFC-0119 and the blog's short-term commitment out another release.
Release gate: #578 must not ship without #261. (Recorded 2026-07-25.) #578 declares the
Dropaspect and enforces its eligibility rules, but destructor invocation is #261. Between them,extend Handle: Drop { fun drop(self) { … } }compiles and the destructor never runs — a feature that looks functional and silently does nothing, which is the failure mode this project has already hit twice elsewhere.This is acceptable only because both issues target v0.12.0, so the gap exists on
developand never in a release. If #261 slips out of v0.12.0, #578 must gain a rejection forDropimpls before release rather than shipping them inert. The same does not apply toCopy: declaring it changes no runtime behaviour either way, since the evaluator already duplicates every value — its observable effect is thatT: Copybounds begin to resolve.Gate fired 2026-07-31. #261 moved to v0.13.0 along with the rest of the ownership block (#262, #267, #268, #269, #271, #273), while #578 had already shipped in v0.12.0. The conditional above is therefore live, and its remedy — rejecting
Dropimpls before release rather than shipping them inert — is tracked as #601, milestoned v0.12.0.Discharged 2026-07-31 (#601), by a narrower rejection than this section's wording. What is rejected is a
dropbody, not aDropimpl. Declaringextend T: Drop { fun drop(self) {} }remains legal.The reason is that "ship
Dropinert" conflates two things this section does not separate. Destructor invocation is missing, and a body that would clean up and never runs is exactly the silent-no-op failure this gate exists to prevent. But everythingDropmeans at the type level is implemented and correct today — theCopy/Dropexclusion (§4),T: DropandT: !Dropbounds, the anonymous-record ban, and §7's refusal to partially move aDropvalue. None of those depend on the destructor running, and a blanket rejection would delete all of them, along with the twelve corpus fixtures that exercise them — every one of which declaresDropwith an empty body precisely because it wants the type-level effect and not a destructor.So the rule is: an empty body claims nothing that is not delivered; a body with statements in it does. The restriction lifts with #261.
9b. Implementation requirement inherited from RFC-0122
Coverage: untestable (see frontmatter). This is an internal reusable-place architecture requirement.
Added 2026-07-24, from answering RFC-0122's open question 3.
Move checking (this RFC) and borrow checking (RFC-0122) are two analyses over one shared place abstraction — Rust's structure, where initialization/move tracking and borrow tracking are separate dataflow analyses over a shared place-and-move-path representation.
The requirement that follows, and it is the only thing RFC-0122 asks of this RFC:
The place abstraction — whatever represents
x,x.f,x.f.gand "reached through a dynamic index" — must be a standalone, reusable component with no move-specific assumptions baked in.
Given that, this RFC ships in v0.12.0 and borrow checking adds a second analysis over the same places in a later release, with no rework. If places are instead folded into move-specific state, the borrow checker has to rebuild them, and the two will disagree about partial moves — which §7's field-granularity tracking makes observable immediately.
Related, and already consistent: RFC-0122 resolved its granularity question to per-field for statically-named fields, whole-value through a dynamic index — which is exactly §7's field granularity plus §9a's array-element ban, arrived at independently. The two documents agree without either being amended to fit the other.
9a. Completeness audit against constructs added since acceptance
Added 2026-07-24 during integration review. This RFC was accepted 2026-06-28, when structs and enums were the only aggregates. Six constructs it does not cover, with proposed resolutions where precedent is unambiguous.
1. Are &T and &var T themselves Copy? — was unspecified anywhere, and was the one blocking gap.
Nothing in this RFC, RFC-0067a, or the rest of the corpus states it. RFC-0067a §205 defers
its T: Copy gate to "RFC-0071's affine/Copy model," and this RFC never mentions references,
so the two documents point at each other. The consequence if &T is affine:
let r := &x;
f(r);
g(r); // error — r was moved into f?
Shared borrows would be single-use, which is unusable. Resolved 2026-07-24: &T is Copy; &var T is
not — Rust's rule, and near-universal. An exclusive reference must stay unique, so it moves
or reborrows; a shared reference has no such obligation. See §9 question 3.
2. Moving out of an array element — no rule, and it is the case static tracking cannot
handle. §7 tracks partial moves "at field granularity". xs[0] has no field; the index may
be dynamic, so which element is gone is not a static fact. Resolved: banned outright.
Rust reaches the same conclusion for the same reason.
3. Partial moves out of a tuple — no rule. §7 is written entirely in terms of struct
fields, but v0.11.0 shipped tuple element assignment (t.0 = v), so let a = t.0; is
writable today. Resolved: identical to struct fields — tuple elements are positional
fields and are statically named, so the machinery applies unchanged.
4. Moving a payload out of an enum variant — no rule. §1 says move semantics apply to enums; §7's partial-move rules never mention them. Resolved: matching a variant and moving its payload consumes the enum wholly, not partially — there is no "rest of the value" to retain, since the other variants were never inhabited.
5. Closure capture — resolved 2026-07-24; the rule already existed and this RFC had not
caught up. The spec states capture is by value, and by-value capture of a non-Copy
type under affine ownership is a move. No design was needed — only noticing that
functions.md had already decided it, and correcting its word "cloned" to match. See §9
question 4.
Items 2, 3 and 4 read "Proposed" until 2026-07-26, and that was stale rather than undecided. All three were adopted normatively when this RFC was integrated — they are the
array elements/tuple elements/enum payloadsrows of the partial-move table inpublic/reference/spec/ownership.md, written in the same commit that moved this RFC to3-integrated. The audit text was simply never updated to match the spec it fed. Marked resolved here so the two documents agree; no decision changed, and per PROCESS.md the spec was already the normative statement of all three.
6. Records. Not a gap in substance — RFC-0117 owns narrowing on partial move, and this RFC correctly says nothing about it. Only §1's scope sentence needed widening, done above.
9. Unresolved questions
-
Copydeclaration syntax — resolved.Copyis declared viaextend T: Copy;. This is consistent with how other aspects are implemented in Metel. A derive-like shorthand (e.g.derive(Copy)) will be considered when the derived aspects system (RFC-0012) is designed; until then, the explicit impl is the only supported form. -
Partial moves and pattern matching — resolved. Pattern destructuring may simultaneously move out of multiple fields, subject to the same rules as sequential partial moves: the compiler tracks moved fields at field granularity,
Droptypes may not be partially destructured, and a partially destructured value may not be used as a whole. Whether individual pattern bindings may borrow rather than move a field (arefbinding modifier or equivalent) is deferred to the pattern syntax RFC. -
AreResolved 2026-07-24:&Tand&var TthemselvesCopy?&TisCopy;&var Tis not. A shared reference carries no obligation — duplicating one grants no capability the holder did not already have, and if it were affine a shared borrow would be single-use, which is unusable. An exclusive reference must stay unique to be exclusive, so it moves or reborrows. This is Rust's rule and it is near-universal.Recorded here because the gap was circular: RFC-0067a defers its
T: Copygate to "this RFC's affine/Copy model", and this RFC did not mention references at all. RFC-0067a's own gate is a separate question and is unaffected — that gate is about reading a referent of typeTthrough a reference, which still requiresT: Copy.&TbeingCopyis about duplicating the reference.Important qualifier, so this resolution is not over-read:
&var T: !Copyis necessary for exclusivity and nowhere near sufficient. It prevents duplicating an exclusive reference:let a = &var x;let b = a; // a is moved — no duplicationIt does nothing about independent creation:
let a = &var x;let b = &var x; // two exclusive references to x — this RFC forbids nothing hereThe second case needs a checker tracking what is currently borrowed, which is neither ownership nor
Copy-ness and is therefore outside this RFC entirely. The rule that makes&varactually exclusive — any number of&T, or exactly one&var T, never both — is stated nowhere in the corpus; it is now RFC-0122's headline. Recorded here because "exclusive references are notCopy" reads like a guarantee of uniqueness and is not one. -
Closure capture semantics are unspecified.Resolved 2026-07-24 — the spec already settled it and this RFC had not caught up.public/reference/spec/functions.mdstates that "closures capture variables from their enclosing scope by value." Under affine ownership, by-value capture of a non-Copytype is a move: the closure takes ownership and the enclosing binding is invalid afterwards. Cloning it instead is precisely what affine ownership forbids.The spec's wording needed one correction, not its rule. It said a captured variable is "cloned into the closure environment", which is accurate for the current everything-clones interpreter and wrong once this RFC is enforced. Now "copied", with a
Planned for v0.12.0marker stating the move rule for non-Copycaptures. RFC-0050 (Closure Capture Lists,4-implementedas of 2026-09-02) may later add explicit capture modes; it is not needed for the default, which follows from by-value capture plus affine ownership. -
Does passing an exclusive reference consume it?Resolved 2026-07-26 — an interim rule, deliberately narrower than RFC-0122's eventual one. Question 3 above says an exclusive reference "moves or reborrows" and then specifies only the move. That gap became load-bearing the moment&var T: !Copywas implemented (#578): with move checking (#579), every use of an exclusive reference is a move, and this — which compiles today — would stop compiling:let r = &var c;bump(r);bump(r); // `r` was moved into the previous callThat is the same failure §9a question 1 used to decide
&Tmust beCopy: "shared borrows would be single-use, which is unusable." The argument transfers to&var, and the mechanism that answers it is reborrowing — which is RFC-0122's scope, and RFC-0122 is0-draftand deliberately out of v0.12.0.The rule, confined to argument position:
Passing a
&var Tvalue as an argument to a parameter of type&var Treborrows it: the reference is borrowed through for the duration of the call, and the original binding remains usable afterwards. Every other use moves, includinglet q = p;, returning a reference, storing one in a struct, and capturing one in a closure.The boundary is set by question 3's own example, not chosen freely. That example requires
let b = a;to move — "ais moved — no duplication" — so any rule broad enough to cover plain binding would contradict a resolution already taken. Argument position is what is left, and it is exactly what the failing case needs.Why an interim rule rather than pulling RFC-0122 forward. Its own question 3 resolved that move and borrow checking split safely across releases given §9b, and concluded "the argument for pulling this RFC into v0.12.0 falls away." Pulling it in would mean three lifecycle transitions, an integration cross-check against the thirteen RFCs that reference borrow checking, and two unsettled questions (lexical vs. non-lexical; whether any of it is observable while the evaluator deep-clones) — to obtain one bullet of it. The narrow rule is a strict subset of what RFC-0122 will specify, so it is subsumed rather than contradicted when that lands.
What this does not do, stated so the rule is not over-read. A reborrow's duration is not tracked, because tracking it is borrow checking. In v0.12.0 the rule does exactly one thing: it stops move checking from consuming the reference. Two
&var Tto one place remain unrejected, as §9 question 3 already records — no guarantee is gained here, and none is lost.
References
- RFC-0024 (Linear Types, superseded) — prior exploration of linear/affine ownership in Metel; this RFC is the settled formulation of the same core idea.
- RFC-0049 (Linear Function Type System, draft) — function-level linearity constraints; orthogonal to but compatible with the value-level move semantics specified here.
- RFC-0063 (Allocator Handles) — depends on affine ownership of
@a T; §2 states the non-Copyproperty of allocator-tagged pointers without grounding it in a prior RFC. - RFC-0066 (Allocated Value Extraction) — the
T: !Dropbound is founded on §3–4 of this RFC. - RFC-0068 (Struct-Owned Allocators) — drop ordering in §5 of this RFC governs when struct fields become unreachable relative to arena freeing.
- RFC-0137 (Nominal Types as Branded Rows,
2-accepted2026-08-27 — reverted from2-accepted2026-08-25, same day, then re-accepted; added 2026-08-25) — supersedes §7's unconditional partial-move-with-Dropban in design with row-bounded dispatch; not yet reflected in--move-check's actual enforcement, see §7's own callout.