Copy and Move

Copying and moving are operations a type has or does not have. By default the compiler generates both, structurally: a structure is copied or moved field by field, an array element by element, a tuple, variant or optional part by part. A type changes that by declaring the operation in an extend block — with a body to supply its own, or without one to prohibit it.

The two special operations

extend T {
    func =(self: &var T, other: &T) { … }     // custom copy
    func =(self: &var T, other: &T);          // copying prohibited
    func <-(self: &var T, other: T) { … }     // custom move
    func <-(self: &var T, other: T);          // moving prohibited
}
Declared in extend TA copy of TA move of T
nothingcopies each partmoves each part
func =(self: &var T, other: &T) { … }runs the bodyunchanged
func =(self: &var T, other: &T);an error — T is move-onlyunchanged
func <-(self: &var T, other: T) { … }unchangedruns the body
func <-(self: &var T, other: T);unchangedan error

A generated operation exists only when every part supports it. A structure with a move-only field is move-only itself, because copying it would copy the field:

error: move-only value 'booking' requires an explicit '<-' in initialization
  note: plain by-value use copies its source, but 'Booking' prohibits copying
  help: write 'let destination <- booking' to transfer ownership

Signatures

The signatures are fixed, and each is checked where it is declared:

MistakeError
func =(self: &var Sheet, other: Sheet)copy special operation for type 'Sheet' must have signature 'func =(self: &var Sheet, other: &Source)'
func <-(self: &var Sheet, other: &Sheet);move special operation for type 'Sheet' must have signature 'func <-(self: &var Sheet, other: Sheet)'
func =(…); outside extendspecial operation '=' may only be declared in an extend block

The parameters must be named self and other, and neither operation takes type parameters, default values or a result. A bodyless declaration in an extend block is a prohibition; a bodyless function in an interface is an ordinary requirement.

Custom copy

A copy with a body runs for every copy of the type. self is the new value, in storage the compiler provides; other is the source, borrowed read-only, so the copy can neither change nor consume it. Once the body exists, nothing is copied field by field any more: every field the copy should have, the body sets.

struct Sheet {
    text: char8[..];
    generation: int32;
}

extend Sheet {
    func =(self: &var Sheet, other: &Sheet) {
        self.text = other.text;
        self.generation = other.generation + 1;
    }
}

func Main() -> int {
    let original = Sheet { text: "Minutes", generation: 1 };
    let copy = original;                   // runs '=': generation 2
    var board = Sheet { text: "Agenda", generation: 1 };
    board = copy;                          // runs '=': generation 3
    PrintLine("{} {} {}", original.generation, copy.generation, board.generation);
    return 0;
}

A copy runs only when the source keeps its value afterwards — a named place, borrowed storage, or a reference. A fresh temporary and a source handed over with <- are not copied: they transfer exactly as they would initialize a binding, and the custom = is not called.

WrittenRuns =?
let copy = original;yes
Show(copy) (by value)yes
board = copy;yes
board = Sheet { … };no — a fresh temporary
board = Make();no — a fresh temporary
Show(<-copy)no — a move

Assigning over a live value is a replacement. The new value is produced first — by the custom = when it copies — then the old value is destroyed, and then the new one is installed. A structure whose field has a custom = copies that field with it, as part of its generated copy.

Copying from another type

A copy with a body may take a different source type: func =(self: &var Celsius, other: &int32) { … }. It is used by an assignment to an existing Celsius, reading = degrees;. It is not a conversion: let reading: Celsius = degrees; is still cannot assign 'int32' to 'Celsius'. Only the exact T-from-T form has a bodyless, prohibiting meaning.

Prohibiting copies

A bodyless = makes a type move-only. That is the right choice for any type that owns a resource — memory, a file, a handle — and does not implement a real independent copy, since two copies would each release the same resource.

struct RoomKey {
    room: int32;
}

extend RoomKey {
    func =(self: &var RoomKey, other: &RoomKey);
}

Every by-value use of a named move-only value then needs <-, and the error names the position and the spelling to use:

WhereRefusedWritten with a moveError ends with
A bindinglet spare = key;let spare <- key;in initialization
An argumentCheckOut(key)CheckOut(<-key)in argument
A returnreturn key;return <-key;in return
A field valueBooking { key: key }Booking { key: <-key }in aggregate
error: move-only value 'key' requires an explicit '<-' in argument
  note: plain by-value use copies its source, but 'RoomKey' prohibits copying
  help: prefix the argument with '<-', as in 'Take(<-key)'

Assignment to a move-only var always uses <-, even from a fresh temporary — key = other; and key = RoomKey { room: 4 }; both fail with copying type 'RoomKey' is prohibited. Write key <- other; or key <- RoomKey { room: 4 };. Initialization from a temporary needs no arrow: let key = RoomKey { room: 4 }; is fine.

Prohibiting moves

A bodyless <- prohibits moving. A value of such a type stays in the storage it was created in. Every <- on it is an error:

error: moving type 'Stay' is prohibited
  note: the type declares its canonical move operation without a body
  help: borrow the value or construct a distinct replacement instead

A type that keeps its copy can still be copied with =. A type that prohibits both cannot receive a value from any expression, a literal included — let pinned = Pinned { id: 1 }; is the same error — so it is built where it lives, one part at a time:

struct Pinned {
    id: int32;
}

extend Pinned {
    func =(self: &var Pinned, other: &Pinned);
    func <-(self: &var Pinned, other: Pinned);
}

func Peek(pinned: &Pinned) -> int32 {
    return pinned.id;
}

func Main() -> int {
    var pinned: Pinned;
    pinned.id = 4;
    PrintLine("{}", Peek(pinned));
    return 0;
}

Custom move

A move with a body runs for every move of the type. other is the source, taken by value: the operation owns it, and like any by-value parameter it is destroyed when the body ends. The body therefore writes the new state into self from other, and whatever other still holds is released with it.

struct Cell {
    id: int32;
}

extend Cell {
    func <-(self: &var Cell, other: Cell) {
        self.id = other.id;
        PrintLine("moved {}", other.id);
    }
}

Partial moves

A field, a tuple element or an array element cannot be moved out of a value on its own. A value that is partly there would leave nothing able to say what to destroy when its owner's scope ends, so a value is moved whole or not at all:

error: cannot move field 'gift' out of droppable value 'parcel'
  note: partial moves would leave the aggregate with only some fields initialized
  help: move the complete aggregate or borrow or clone the component explicitly

The same error names a tuple element (field '0') and an array element (indexed element [0]). What works instead is taking the whole value apart with a moving destructuring pattern, so that every part gets exactly one new owner:

struct Item {
    name: char8[..];
}

extend Item {
    func =(self: &var Item, other: &Item);

    func ~Item(self: &var Item) {
        PrintLine("{} destroyed", self.name);
    }
}

struct Parcel {
    wrapping: Item;
    gift: Item;
}

func Unwrap(parcel: Parcel) -> Item {
    let Parcel { wrapping: _, gift: gift } <- parcel;
    return <-gift;
}
  • A part bound to a name belongs to that binding and is destroyed with it.
  • A part bound to _, or a structure field the pattern leaves out, has no owner left, so it is destroyed on the spot — at the let, or at the top of a match arm — in the order the parts appear in the pattern.

A destructuring let owns what it takes apart, so a move-only value is handed to it with <-; = parcel fails with move-only value 'parcel' requires an explicit '<-' in initialization. Tuples may always be split this way. A structure may be split only when it declares no destructor of its own, because a destructor runs on the whole value:

error: cannot split 'Sealed' with a moving pattern, because it declares destructor '~Sealed'
  note: '~Sealed' runs on the whole value, so no part of it can be taken out on its own
  help: bind the whole value and read its fields, or match a value that stays with its owner
You wantWriteAllowed when
one field, moved out<-parcel.giftnever
every part, each to one ownerlet Parcel { wrapping: _, gift: gift } <- parcel;the structure has no destructor
to read a fieldparcel.gift.namealways — reading moves nothing

Matching and ownership

A match that binds parts of its subject takes them, the same way a destructuring let does. match <-value { … } hands the subject over; a temporary subject is taken directly. A match that binds from a named copyable value without <- matches a copy of it, which its arms own; the original stays with its owner. A match that binds nothing, or matches through a reference, takes nothing. A move-only named subject has no copy to match, so an arm that binds from it needs the subject handed over; match box { .Full(item) => … } fails with move-only value 'box' requires an explicit '<-' in match subject, and the help says transfer the subject with 'match <-box'.

See also