Note: None of this has been implemented.

Values are copied as they flow from variable-to-variable or function-to-function. Changes made to a copy don'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 for which we don't want multiple copies to exist at the same time. Such "non-copyable" values can be useful:

To provide safe support for such "non-copyable" values, Cone supports move semantics. Move semantics enable non-copyable values to be moved from one place to another in a code's logic. However, once the value moves, it may no longer be accessed from its old location. This restriction ensures there is only ever one copy of the value in existence.

Move vs. Copy Types

Whether a value is handled by move vs. copy semantics is determined by its type. In general, Cone treats most types as having copy semantics, unless it detects some reason why move semantics restrictions are needed. Here are the rules:

  1. If the type declares that it uses move semantics, then it does. This is as simple as adding the @move attribute on the type declaration:
    struct @move Handle:
       id i32
    
  2. If the type implements an initializer method that essentially enables the creation of a copy, the type's values have copy semantics. Since the compiler automatically uses this cloning method (when implemented) to make all copies of values of this type, these values are clearly copyable.
  3. If the final method is implemented, the type's values have move semantics. If the type requires a finalizer to clean up dependencies, chances are excellent that creating copies will open the door to safety issues.
  4. If any of the type's fields have move semantics, the type's values have move semantics. This is because if part of a value cannot be copied, the whole value certainly cannot be either.
  5. If any of the type's fields are references to other data structures, the type's values have move semantics. This is because it is not immediately obvious how to automatically and safely create copies of this type's values.
  6. If the type is marked as having move semantics, that's what it gets.
  7. If none of the above apply, the type's values have copy semantics.

Move Capabilities and Restrictions

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.

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

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 either the swap or left-assignment operator:

Swap Operator

The swap operator may be used on copy or move values:

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.

Left-Assignment

Assignment is an expression whose value (for copy semantics) is a copy of the value of the right-hand expression. With move-only semantics, it can be very helpful to have the value of the assignment expression be the value previously stored in the left-hand (lval) memory location. To obtain this result, use the left-assignment operator (:=).

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

_