Values are usually copied as they flow from function-to-function or variable-to-variable. Changes made to a copy won't alter the original value.
imm a = 3 imm b = a // b holds a copy of a's value b += 1 // b's value is changed to 4 a // is still 3
Values of certain types, however, may not be copied. Such values may only be moved.
This "no-copy" restriction is typically oriented around non-aliasable references, making possible some useful capabilities:
- Safe, single-owner memory management, free of runtime bookkeeping costs
- Threads able to locklessly exchange mutable values
Despite their usefulness, 'move' values do introduce some complications, as they don't always behave like 'copy' values. In particular, moving a value deactivates its source variable and changes its scope, thereby delaying or accelerating its demise. This page explains these important behaviors.
Copy vs. Move Types
A value's type dictates whether its values are treated as 'copy' or 'move'. By default, most types are 'copy'. The 'move' types include:
- Allocated references using the 'lex' allocator.
- Any reference using the 'uni' or 'mut1' permission.
- Any compound type (e.g., struct, array or variant) in which any field or element type is 'move'.
- A stack-allocated variable whose type implements the 'drop' method.
Note: Any user-defined type that implements the .clone method makes that type 'copy', even if it would otherwise be 'move'. This is because the .clone method is considered a copy constructor which implements a safe way to copy this type's values.
Let's see what happens when we do a 'move':
imm nbr = &own 5 imm newvar = nbr // the ref has been moved to newvar imm b = *nbr // **ERROR** nbr no longer has a usable value
We have a simple reason for deactivating the variable 'nbr' after the move. If we had not done so, we would effectively end up with two copies of a value that is supposed to be non-copyable. By deactivating 'nbr', we ensure there is only ever one usable copy in existence.
The same principle holds true when passing a 'move' value to another function.
imm nbr = &own 5 afunction(nbr) // the ref is moved to the called function anotherfunc(nbr) // **ERROR** nbr is deactivated
Borrowing references can deactivate variable
Source variables are also deactivated whenever a borrowed reference is created to (or out of) a 'move' value. This deactivation is only temporary; the original variable's use is restored after the borrowed reference expires:
imm nbr = &own 5 do imm nbrref &u32 = nbr // Create a borrowed reference // nbr is no longer usable in this Scope afunction(nbr) // nbr is usable again now that nbrref is gone
Sometimes a move is not allowed, even on a 'move' value. This happens whenever it is not possible to cleanly deactivate the source of the value. For this reason, you may not move a 'move' value out of any kind of array or by derefencing it out of a borrowed reference.
It is possible to move a 'move' value out of a particular struct's field, but doing so deactivates use of the entire original struct.
Swapping 'move' values
swap may be used to safely swap two 'move' values:
mut a = &own 5 mut b = &own 6 swap a,b // a now points to 6 and b points to 5
swap offers a helpful way to get around forbidden moves. One can temporarily extract a 'move' value out of a borrowed reference or array by swapping it with some arbitrary type-compatible value. After making use of the extracted value, simply swap it back in.
fn func(arrref & &own u32, index u32) mut work = &own 0 // Set up the work area with a dummy value swap work, arrref[index] // Extract the desired 'move' value imm retval = *work // Make use of the extracted 'move' value swap work, arrref[index] // Restore the original value back into the array retval
The reason why this works is that the original source always has a usable 'move' value, (even if it is only a dummy value). That ensures that the original source is always completely safe to use.
Scope escapes and Destruction
In addition to variable deactivation, moving a value effectively changes its lexical scope to that of its new variable that owns it. Why this matters is that 'move' values are effectively dropped and/or destroyed at the end of the last scope they are moved into. So, by allowing a move value to hop from scope to scope, you effectively lengthen or shorten its effective lifetime.
It is legal for 'move' values to move (or not) based on conditional decisions made at run-time. These are called conditional moves.
There is an easy way to ensure the immediate destruction of a 'move' value. Just assign it to the anonymous variable "_":
imm ref = &own 5 _ = ref callfn(ref) // **ERROR**. ref is deactivated
Doing this on a 'copy' value does nothing.