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, new expression)

  • 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

= (copy)

<- (move)

:= (clone)

int, float, POD scalars

✓ (becomes copy)

string

array<T>

table<K;V>

Struct (all POD fields)

✓ (becomes copy)

Struct (has array/table fields)

tuple

depends on elements

depends on elements

depends on elements

variant

depends on elements

depends on elements

depends on elements

Raw pointer

smart_ptr

lambda

block

iterator

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

& x

ref(x)

Reference

Variable must outlive the lambda. May require unsafe.

= x

copy(x)

Copy

Type must be copyable.

<- x

move(x)

Move

Type must be moveable. Source is zeroed.

:= x

clone(x)

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 with var 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.