2.24. Move, Copy, and Clone
Daslang has three assignment operators that control how values are transferred between variables. Understanding when to use each one is essential for writing correct code.
Operator |
Name |
Effect |
|---|---|---|
|
Copy |
Bitwise copy. Source remains unchanged. |
|
Move |
Transfers ownership. Source is zeroed. |
|
Clone |
Deep copy. Source remains unchanged. |
2.24.1. Copy (=)
The copy operator performs a bitwise copy of the right-hand side into the left-hand side. The source value is not modified:
var a = 10
var b = a // b is now 10, a is still 10
Copy works for all POD types (int, float, bool, string, pointers, etc.) and for
structs whose fields are all copyable.
Types that manage owned resources — array, table, lambda, and iterator — cannot
be copied. Attempting to copy them produces:
// error: this type can't be copied, use move (<-) or clone (:=) instead
2.24.1.1. Relaxed assign
By default, the compiler automatically promotes = to <- when:
The right-hand side is a temporary value (struct literal, function return value,
newexpression)The type cannot be copied but can be moved
This means you can often write = and the compiler will do the right thing:
var a : array<int>
a = get_data() // automatically becomes: a <- get_data()
This behavior is controlled by the relaxed_assign option (default true).
Set options relaxed_assign = false to require explicit <- in all cases
(see Options).
2.24.2. Move (<-)
The move operator transfers ownership of a value. After a move, the source is zeroed:
var a : array<int>
a |> push(1)
a |> push(2)
var b <- a // b now owns the array, a is empty
Move is the primary way to transfer containers and other non-copyable types.
2.24.2.1. When to use move
Use <- when:
You are done with the source variable and want to transfer its contents
You are initializing a variable from a function return value
You are passing ownership into a struct field or container
def make_data() : array<int> {
var result : array<int>
result |> push(1)
result |> push(2)
return <- result // move out of the function
}
var data <- make_data() // move into the variable
2.24.2.2. Return by move
Functions that return non-copyable types must use return <-:
def make_table() : table<string; int> {
var t : table<string; int>
t |> insert("one", 1)
return <- t
}
A regular return would attempt to copy the value, which fails for non-copyable types.
2.24.3. Clone (:=)
The clone operator creates a deep copy of the right-hand side. Unlike = (which is a
shallow bitwise copy), := recursively clones all nested containers and non-POD fields:
var a : array<int>
a |> push(1)
a |> push(2)
var b : array<int>
b := a // b is a deep copy; a is unchanged
After cloning, a and b are completely independent — modifying one does not affect the other.
(see Clone for implementation details and auto-generated clone functions).
2.24.3.1. When to use clone
Use := when:
You need to duplicate a container (array, table)
You need an independent copy of a struct that contains non-copyable fields
You want both the source and destination to remain valid after the operation
2.24.3.2. Clone initialization
You can clone-initialize a variable at declaration:
var a : array<int>
a |> push(1)
var b := a // clone a into a new variable b
This expands into:
var b <- clone_to_move(a)
where clone_to_move creates a temporary clone and moves it into the new variable.
For POD types, clone initialization is optimized to a plain copy.
2.24.4. Type Compatibility
The following table summarizes which operators work with which types:
Type |
|
|
|
|---|---|---|---|
|
✓ |
✓ |
✓ (becomes copy) |
|
✓ |
✓ |
✓ |
|
✗ |
✓ |
✓ |
|
✗ |
✓ |
✓ |
Struct (all POD fields) |
✓ |
✓ |
✓ (becomes copy) |
Struct (has array/table fields) |
✗ |
✓ |
✓ |
|
depends on elements |
depends on elements |
depends on elements |
|
depends on elements |
depends on elements |
depends on elements |
Raw pointer |
✓ |
✓ |
✓ |
|
✗ |
✓ |
✓ |
|
✗ |
✓ |
✗ |
|
✗ |
✗ |
✗ |
|
✗ |
✓ |
✗ |
A struct, tuple, or variant is copyable/moveable/cloneable only if all of its fields are.
2.24.5. Variable Initialization
The three initialization forms correspond to the three operators:
var x = expr // copy initialization
var x <- expr // move initialization
var x := expr // clone initialization
For local variable declarations, the compiler checks the type and reports an error if the chosen initialization mode is not supported:
var a = get_array() // error if relaxed_assign is false:
// "local variable can only be move-initialized; use <- for that"
2.24.6. Struct Initialization
In struct literals, each field can use a different initialization mode:
struct Foo {
name : string
data : array<int>
}
var items : array<int>
items |> push(1)
var f = Foo(name = "hello", data <- items)
Here name is copy-initialized and data is move-initialized. After this, items is empty.
Clone initialization is also supported in struct literals:
var f2 = Foo(name = "hello", data := items)
After this, items still contains its original data.
2.24.7. Lambda Captures
Lambda capture lists support all three modes. The capture keyword introduces the capture
list, with each entry specifying a mode and a variable name:
def make_lambda(a : int) {
var b = 13
return @ capture(= a, := b) (c : int) {
debug(a)
debug(b)
debug(c)
}
}
Here = a captures a by copy and := b captures b by clone.
Each capture entry uses an operator prefix to specify the mode:
Shorthand |
Named |
Mode |
Requirement |
|---|---|---|---|
|
|
Reference |
Variable must outlive the lambda. May require |
|
|
Copy |
Type must be copyable. |
|
|
Move |
Type must be moveable. Source is zeroed. |
|
|
Clone |
Type must be cloneable. |
Multiple captures are separated by commas:
return @ capture(= a, <- arr, := table) () {
// a is copied, arr is moved, table is cloned
}
Generators also support captures:
var g <- generator<int> capture(= a) {
for (x in range(1, a)) {
yield x
}
return false
}
(see Lambdas).
2.24.8. Containers
array and table types cannot be copied. This is because a bitwise copy would create two
variables pointing to the same underlying memory, leading to double-free errors.
To transfer a container, use move:
var a : array<int>
a |> push(1)
var b <- a // a is now empty
To duplicate a container, use clone:
var a : array<int>
a |> push(1)
var b : array<int>
b := a // independent deep copy
Clone of array<T> resizes the destination and clones each element. Clone of table<K;V>
clears the destination and re-inserts each key-value pair.
2.24.9. Classes
Copying or moving class values requires unsafe:
class Foo {
x : int
}
unsafe {
var a = new Foo(x=1)
var b = *a // copy requires unsafe
}
2.24.10. Custom Clone
You can define a custom clone function for any type. If a custom clone exists, it is called
by the := operator regardless of whether the type is natively cloneable:
struct Connection {
id : int
socket : int
}
def clone(var dest : Connection; src : Connection) {
dest.id = src.id
dest.socket = open_new_socket() // custom logic instead of bitwise copy
print("cloned connection {src.id}\n")
}
var a = Connection(id=1, socket=42)
var b : Connection
b := a // calls custom clone
(see Clone for auto-generated clone functions for structs, tuples, variants, arrays, and tables).
2.24.11. Quick Reference
Here is a complete example showing all three operators:
options gen2
def make_data() : array<int> {
var result : array<int>
result |> push(1)
result |> push(2)
result |> push(3)
return <- result
}
[export]
def main {
// Copy (scalars)
var a = 10
var b = a
print("copy: a={a} b={b}\n")
// Move (containers)
var data <- make_data()
print("data = {data}\n")
var moved <- data
print("moved = {moved}\n")
print("data after move = {data}\n")
// Clone (deep copy)
var cloned : array<int>
cloned := moved
cloned |> push(4)
print("cloned = {cloned}\n")
print("moved = {moved}\n")
}
Expected output:
copy: a=10 b=10
data = [[ 1; 2; 3]]
moved = [[ 1; 2; 3]]
data after move = [[]]
cloned = [[ 1; 2; 3; 4]]
moved = [[ 1; 2; 3]]
- I want to transfer ownership (source becomes empty):
Use
<-(move)- I want an independent deep copy (both remain valid):
Use
:=(clone)- I want a simple value copy (POD types):
Use
=(copy)- I’m returning a container from a function:
Use
return <-- I’m initializing a variable from a function call:
Use
var x <- func()or rely on relaxed assign withvar x = func()- The compiler says “this type can’t be copied”:
The type contains arrays, tables, or other non-copyable fields. Use
<-to move or:=to clone.
See also
Clone for custom clone operator implementation, Finalizers for delete-after-move semantics, Arrays and Tables for non-copyable container types, Structs and Classes for struct and class copy/move rules, Temporary types for temporary-type clone rules.