Skip to content

Move Expressions

Accepted

Accepted for V1 source semantics; ownership analysis verification is tracked in Scope Backlog.

move is an explicit ownership-transfer expression:

var b = move a
consume(move b)
return move c

move applies only to movable places in V1:

  • local var bindings
  • local const bindings
  • by-value parameters, including by-value self
  • loop bindings

It does not apply to fresh rvalues, fields, array or slice elements, optional payloads, dereferenced pointers, globals, module bindings, or captured outer variables in V1:

consume(make_file())      // ok: fresh rvalue

move requires a movable local place:

consume(move make_file()) // error: move requires a movable place

move owner.field // error in V1
move items[i]    // error in V1
move ptr.*       // error in V1

move place produces the moved value and marks the source binding uninitialized on that control-flow path. Use after a moved or maybe-moved binding is a hard error. A moved var binding may be reassigned; a moved const binding cannot be reassigned and remains unusable.

move has low precedence and applies to the following movable place expression. When a moved value is used as a method receiver, write the moved receiver explicitly with parentheses:

var erased = (move concrete_box).into_dyn(Iterator(i32))

Ambiguous forms such as moving the result of a method call should be rejected or linted until the expression grammar is finalized:

move concrete_box.into_dyn(Iterator(i32)) // unclear; do not use in V1

Plain assignment, function argument passing, aggregate literal fields, and return value are copy-like in the semantic model. If the source binding's type has copying forbidden, these forms are compile errors unless the source expression is a fresh rvalue or uses explicit move:

consume(file)      // copy; error if copy is forbidden
consume(move file) // transfer

return file        // copy; error if copy is forbidden
return move file   // transfer
return File.open() // ok: fresh rvalue

move is valid for copyable and non-copyable types. It always moves and invalidates the source binding; it never secretly means copy. Moving a trivially copyable value may be linted as unnecessary unless it is useful for generic code or intentionally ending a binding's lifetime.

Move analysis is definite-assignment style, not borrow checking. The compiler tracks local binding states as initialized, moved/uninitialized, or maybe moved:

var file = make_file()

if cond {
  consume(move file)
}

use(file) // error: maybe moved

Expression evaluation order is deterministic so move effects are predictable:

  • function arguments evaluate left-to-right
  • aggregate literal fields evaluate in source order
  • assignment evaluates the right-hand side before storing
  • exact complex place evaluation order is finalized with full expression semantics

move may appear in conditionals, short-circuit expressions, and loops. Normal move analysis applies. Moves in complex boolean conditions may be linted when they make lifetime state hard to read.