Moves: values there can only be one of
On this page
You have a value there can only be one of: the one handle to an open file, the one owner of a block of memory, the one baton a relay team carries. A copy would make two, so Cone moves such a value instead: handing it on leaves the giver with nothing. That is a restriction, and this recipe gathers the ways of working with it: which types move, what a move leaves behind, how to hand a value on and get it back, and how to get one out of a struct, an array or a reference. Here a team passes its one baton. The baton has a finalizer, the method named final, which runs by itself when the value dies (Ending a value, in the previous recipe, has finalizers in full):
mod relay;
import stdio use *;
// A relay team has one baton. Its finalizer makes it a type that moves:
// handing it on leaves the giver with nothing
struct Baton {
pub legs i32;
fn final(self &uni) {
print <- "the baton is put away after ", legs, " legs\n";
}
}
// A runner takes the baton, runs a leg, and hands it back on
fn run(name &str, b Baton) Baton {
var held = b;
held.legs += 1;
print <- name, " runs leg ", held.legs, "\n";
held;
}
fn main() {
var baton = new Baton(0);
each name in ["Ama", "Bo", "Cy", "Dee"] {
baton = run(*name, baton); // out to the runner, and back again
}
print <- "the team finishes\n";
} // the one baton is finalized here, once
It prints:
Ama runs leg 1 Bo runs leg 2 Cy runs leg 3 Dee runs leg 4 the team finishes the baton is put away after 4 legs
Try this: take away baton =, so the runner keeps the baton, and predict what the second runner gets. The compiler refuses it: This variable's value may have been moved out on a path that reaches here (a branch, or an earlier pass of a loop).
Each call moves the baton into run, which leaves baton empty; run hands it back as its answer, and the assignment fills baton again. However many legs are run, there is one baton, and it is finalized once. You met the same thing with a String, which moves because it owns its text.
Which types move, and why#
A copy is a second, independent value: right for a number, trouble for some values. Copy a file handle, close the file through one copy, and the other reaches nothing, or a different file opened later in its place. A complex value may have no faithful copy, or making one would mean allocating memory or opening resources, which a silent copy should not do. And one holder pays back: the value can be freed the moment its holder goes, decided when the program is compiled (the single owner in Memory), and changeable data can be handed to another thread with no lock, since nobody is left behind to race with it.
Whether a value copies or moves is decided by its type, never by how it is used. A type moves if:
- It says so, by naming Move among the traits it declares. A theatre ticket
copied would let two people into one seat:
struct Ticket is Move { pub seat i32; }A type declaring AtomicValue, the marker of an atomic value, moves too: a copy would be a second value that no atomic operation on the first could reach. - It has a finalizer, as the baton does. A type that cleans up after itself almost certainly owns something, and each copy would clean it up again.
- Any of its fields moves. If part of a value cannot be copied, the whole value cannot be. A struct holding a List of itself moves without declaring it, and so does each of two types that hold a List of the other.
- It is an enum and any of its variants moves, since a value of the enum may be that variant (Enums).
- It is a unique reference, the one and only way to its value, as the Uni[str] of Handing text on is. In Memory's terms that is an owning reference whose permission forbids sharing it (uni), or whose region declares Move, as Uni does (The single owner). By the rule for fields, anything holding one moves too.
Anything else copies. Not built yet: the design also has a type move when any of its fields is a reference to other data, since such a value has no obvious safe copy. Today a struct whose fields are borrowed references or shared owning references (&, &mut, Rc[mut, T], Rc[imm, T]) copies; only a Uni or uni field makes it move.
Declaring is Copy is an assertion, not an instruction: it changes nothing on a type that copies anyway, and is refused on one the rules make move:
struct Point is Copy { // accepted: nothing here moves
pub x i32;
pub y i32;
}
struct Lap is Copy { // Error 1167: Lap is declared Copy, but its 'final' makes it move:
fn final(self &uni) { } // each copy would be finalized.
}
A clone method changes none of this, since no copy calls it (Ending a value). Copy and Move are traits rather than attributes because other code may ask about them: a generic asks with where T is Move (Generic constraints, in Modularity). A function's body cannot ask: v is Move there tests an enum's variant, and is refused on a value with no variants.
What a move leaves behind#
A move may well copy the value's bytes; what it guarantees is that only one holder can use them afterwards. It does that by deactivating the source: once a value has moved out of a variable, the variable may not be used again, neither read nor borrowed, until it is given a new value, as baton was in the relay. That holds whether the value was assigned to another variable, handed to a function, or handed on as the value of a block, an if or a loop:
fn store(b Baton) { print <- "stored\n"; } // takes the baton: it dies when store ends
fn look(b &Baton) { } // only borrows it
let baton = new Baton(0);
let kept = { baton; }; // the block hands baton on: it moves
store(baton); // Error 1048: This variable's value has been moved out.
A value a function returns is handed to its caller, with no source left behind to deactivate.
Moves on some paths: drop flags#
A value may move on one path and not another:
fn practice(raining bool) {
let baton = new Baton(0);
if raining {
store(baton); // moved on this path only
}
print <- "practice ends\n";
} // put away here only if it did not rain
The baton is still finalized exactly once, by whoever holds it at the end. practice(true) prints stored, then the baton is put away after 0 legs as store's parameter dies, then practice ends; practice(false) prints practice ends and then the baton's line. To know which happened, the compiler keeps a hidden drop flag beside such a variable, set where its value arrives and cleared where it moves out, and tests it at the end of the block. A variable moved on every path, or on none, needs no flag and costs nothing. The paths may part in an if or a match, in a loop that may not run, or at an early return or break, and the same flag serves a variable given a new value on only some paths (A value assigned over).
What the compiler will not do is guess. Where the paths join, the value may or may not be there, so it assumes the worse: adding look(&baton); before print is refused (Error 1048: This variable's value has been moved out) until the variable is given a new value. A move inside a loop is the same, since a value moved in one pass is gone in the next; that is what the Try this above ran into.
From scope to scope#
A moving value can be pictured as surfing from scope to scope, its life lengthened each time it is handed on, as the baton was from runner to runner. When it fails to surf out of its last scope, it is finalized at that scope's end, since nothing can use it any more. When a value dies, in Memory, has the exact moment for every kind of value.
Not built yet: ending a moving value early by assigning it to the anonymous variable, _ = baton;, after which baton would be deactivated. Today the name _ is not recognised (Error 1019: The name _ does not refer to a declared name). On a value that copies it would have no effect.
Moving a value out of a struct, an array or a reference#
Moving a whole struct or array is always allowed, and takes every field or element with it. Moving one piece out is another matter, because something would be left with a hole in it:
- A field of a struct, or an element of a tuple. The struct could no longer be used whole, and finalizing it would have no sound answer for the missing part. This holds wherever the struct is (a variable, a parameter, a self taken by value, a temporary, behind a reference), wherever the value would go (a variable, a call, a literal, back to a caller, or out as the value of a block, an if or a loop), and for anything reached through a field, such as what an owning reference in a field points at.
- An element of an array (batons[0], grid[1][0]), wherever the array is, a Uni's included. The elements left behind would never be finalized; the refused move leaves the array as it was.
- Through a borrowed reference, such as the self &mut a method is lent (Borrowed references, in Memory, has them in full), whatever its permission, including calling through it a method whose self is taken by value. A borrow does not own the value: the place it borrows from does, and still holds the value when the borrow ends.
- Through a shared owning reference, one that may be copied, such as a counted Rc[mut, T], Rc[imm, T] or Rc[ro, T], or one into a region that declares neither aliasRef nor Move. The other holders would still point at the value. A sole owner may give its value up, though: a Uni, or an Rc[uni, T] (which is what Rc[T] means). That deactivates the owner and leaves it hollow: its memory is still freed, but nothing that moved is finalized there.
struct Team {
pub baton Baton;
pub lane i32;
}
let team = new Team(new Baton(0), 4);
let b = team.baton; // Error 1166: May not move field 'baton' out of the struct that holds it.
// Swap a value in with '<=>', or move the whole struct.
let moved = team; // allowed: the whole struct moves, its baton with it
fn first(batons Array[Baton, 2]) {
let b = batons[0]; // Error 1121: May not move a value out of an element of an array,
} // which would be left with a hole in it. Swap a value in with '<=>',
// or move the whole array.
fn borrowed(r &mut Baton) {
store(*r); // Error 1120: May not move a value out through a borrowed reference,
} // which does not own it.
fn shared(r Rc[mut, Baton]) {
store(*r); // Error 1120: May not move a value out through a shared owning
} // reference, which does not solely own it.
fn sole(owner Uni[Baton]) {
store(*owner); // allowed: the sole owner gives its value up
}
One case is a move of the whole value in disguise, and is allowed: a match that takes what a variant holds out through its binding (all of a variant declared as a type, such as Some, or the one field of a struct variant), where only the match holds the matched value (a call's answer, such as names.pop(), or a variable the match consumes), and the variant holds nothing else and has no finalizer of its own. Nothing is left with a hole, and nothing is finalized twice:
match names.pop() {
case var s Some { let last = s; print <- last.view(), "\n"; }
case is None { }
}
A variant with two fields, or with a finalizer of its own, is still refused, and so is taking the contents twice. This is what lets each take an item that moves out of a cursor's Option, as drain does.
Getting a value out anyway: swap and :=#
Those are real limits on keeping moving values in structs and arrays, and two operators get around them by never leaving a hole. The swap operator, <=>, exchanges two values, whether they copy or move. To take a moving value out of a field or element, swap another value of its type in, use what came out, and swap it back if you like. Neither value is copied, and the struct always holds something whole:
var team = new Team(new Baton(3), 4); var spare = new Baton(5); team.baton <=> spare; // the team now holds baton 5, and spare baton 3
The left-assignment operator, :=, stores a new value and gives back the one it replaced. (Ordinary assignment, used as an expression, gives a copy of the new value, which a moving value cannot give.)
var old = team.baton := new Baton(6); // the team holds baton 6, and old the one it held
A generic type that moves for some instances#
A generic type, one declared with a type parameter as List[T] is (Generics, in Modularity, shows how), moves exactly when its parameter does if it holds a value of it, by the rule for fields: a Box[i32] holding an i32 copies, and a Box[Ticket] moves. But a type may stand for a value it does not hold. A future, which keeps an answer that will arrive later, holds a shared reference to the cell the answer will be put in, and a shared reference copies whatever it points at. Such a type says when it moves with a condition after its Move, following the word if:
struct Cell[T] {
pub v T;
}
struct Future[T] is Move if T is Move {
pub c Rc[imm, Cell[T]];
}
let f = new Future[i32](new Rc[imm, Cell[i32]](new Cell[i32](5)));
let f2 = f; // a copy: i32 does not move, so neither does Future[i32]
let f3 = f; // another copy; f is still there
let g = new Future[Ticket](new Rc[imm, Cell[Ticket]](new Cell[Ticket](new Ticket(7))));
let g2 = g; // moves g: Ticket moves, so Future[Ticket] does
let g3 = g; // Error 1048: This variable's value has been moved out.
So the answer inside a future of something that moves can be taken only once, and a future of something that copies is copied freely. The actors package's Future[T] is declared this way (Futures).
Each instance decides the condition with its own type arguments. A condition can add Move, never take it away: where it fails, the instance copies unless another rule makes it move. It is written as a where clause is, clauses T is Name joined with and and or and grouped by parentheses, each about one of the type's own parameters: struct Pair[A, B] is Move if B is Move asks about the second only. Copy takes no condition, and nor does a type with no type parameters. The condition belongs to the entry it follows; a where clause after the is list is a requirement on the whole type instead: struct Future[T] is Move where T is Move makes every instance move, and refuses Future[i32] outright (Error 1186: Future requires T is Move, and i32 is not Move.).
You can now tell which of your types move, and work with a value there can only be one of: hand it on, get it back, swap it out of a struct, and know it is finalized exactly once. Working on one value part by part, moved or not, usually means writing its name in front of every line; the next recipe saves that: with blocks.