Note: None of this has been implemented.

Values are usually copied as they flow from variable-to-variable or function-to-function. Changes made to a copy won't affect the original value.

imm a = 3
mut b = a  // b holds a copy of a's value
b += 1     // b's value is changed to 4
a          // is still 3

Sometimes, however, we have values that we can't or shouldn't copy. Uncopyable values can be useful:

This page explains what makes a value copyable or not, and then describes how move semantics allow an uncopyable value to move from one part of a program to another.

Move vs. Copy Types

Whether a value is copyable or not is largely determined by the value's type. The compiler uses heuristics to infer whether or not a type's values are copyable, based on examining the fields and methods it implements:

  1. If the clone method is implemented, the type's values may be copied. Since the compiler automatically uses the clone method (when implemented) to make all copies of values of this type, these values are clearly copyable.
  2. If the final method is implemented, the type's values may not be copied. If the type requires a finalizer to clean up dependencies, chances are excellent that creating copies will open the door to safety issues.
  3. If any of the type's fields cannot be copied, the type's values may not be copied. This makes sense: if part of a value cannot be copied, the whole value certainly cannot be either.
  4. If any of the type's fields are references to other data structures, the type's values may not be copied. This is because it is not immediately obvious how to automatically and safely create copies of this type's values.
  5. If none of the above apply, the type's values may be copied.

Move Semantics

Even when we want to prohibit the copying of these values, we still want to be able to move such values around from variable-to-variable or function-to-function. This capability is called move semantics, which differs from the copy semantics we have worked with so far. Non-copyable values use move semantics. Copyable values use copy semantics.

Moving values around looks just like copying, but adds restrictions to ensure multiple copies of the value are never made. These restrictions, if not well understood, can make it frustrating to write programs that do what is desired in a way that the compiler deems acceptable.

Source Deactivation

It would be misleading to think that moving a value does not copy it. In fact, a "shallow" copy of the value might well be made on every move. What the move restrictions guarantee is that only one version of the value ever exists at a time.

This guarantee is implemented by deactivating the value's source as part of the move. When the source is a variable, this prohibits post-move access to that variable's value:

imm socket = Socket() // Open a new network socket (move semantics)
imm sock2 = socket    // Move the value to sock2
socket.poll()         // **ERROR** socket no longer has an accessible value

By de-activating use of the variable socket after the move, we effectively have only one "copy" of its value, which is now in sock2. If we had not deactivated socket, we would then have two copies of a non-copyable value.

The same deactivation mechanism applies when passing a non-copyable value to another function.

imm socket = Socket() // Open a new network socket (move semantics)
afunction(socket)     // the socket is moved to the called function
anotherfunc(socket)   // **ERROR**: socket is deactivated and unusable

Deactivation of the source happens even for conditional moves, where one branch of conditional move the value but another does not. When the branches join together again, the compiler can't know whether the value is there or not, so conservatively, it assumes the value is gone.

A non-copyable value may be returned by a called function. There is no need to deactivate the value's source, since it is already gone.

(Note: A deactivated source variable may be re-activated at any point by giving it a new value.)

Scope-surfing and Destruction

This movement of a non-copyable value can be understood as shortening or lengthening the value's lifetime as it surfs from one scope to another. When it fails to surf out of its last scope, the value will be destroyed at the end of that scope, as it is no longer useful.

To destroy a move value more quickly, just assign it to the anonymous variable "_":

imm socket = Socket() // Open a new network socket (move semantics)
_ = socket
callfn(socket)        // **ERROR**. socket is deactivated

Doing this on a copyable value has no effect.

Field/Element Moves and Swaps

If we want to move a non-copyable value out of a struct or array, things get more complicated, as other values are now potentially affected by such a move:

These are significant limitations on the use of non-copyable values in structs and arrays. However, these limitations can be ameliorated by using the swap operator:

struct Connection
  s Socket
  b i32
mut conn = Connection[Socket(), 10]
mut x = Socket()  // Open another socket
conn.s <=> x
// conn now contains the new socket and x contains the old one

Swapping offers a helpful way to get around restrictive moves. One can temporarily extract a 'move' value out of a field or array element by swapping it with some arbitrary type-compatible value. After making use of the extracted value, simply swap it back in.

The reason why this works is that we end up with no copies of either value and the source still has a valid value (even if it is only a dummy value). That ensures that the original source stays safe to use.

Assignment

Assignment is an expression whose value (for copy semantics) is a copy of the value of the right-hand expression. With a move-only value, however, the value of the assignment expression is the value previously stored in the left-hand (lval) memory location.

mut conn = Connection[Socket(), 10]
mut x = conn.s = Socket()
// conn now contains the new socket and x contains the old one

_