Skip to main content

Move, Copy, and Clone

Zynx provides three ways to duplicate a value, each with different semantics and performance characteristics.

Move

By default, Zynx uses move semantics for complex types like objects, strings, and Unique<T>. When a value is "moved", ownership is transferred to a new binding, and the original binding can no longer be used. This is a cheap operation as it only involves copying a pointer, not the underlying data.

let a = try String.owned("hello");
let b = a; // a is moved to b
// a can no longer be used here

Unique<T> follows the same rules — transferring a Unique<T> moves ownership of the heap allocation:

let a = Unique(Point { x: 1, y: 2 });
let b = a; // a is moved to b
// a can no longer be used; b owns the allocation

Copy

For simple types that can be safely duplicated with a bit-for-bit copy, Zynx provides the Copy interface. Numeric types are Copy by default. You can implement the Copy interface for your own structs, but there are some restrictions:

  • A struct that implements Copy cannot also have a drop method.
  • All fields in a Copy struct must also implement Copy.

Copy is a marker interface with no methods to implement:

struct Point: Copy {
x: i32,
y: i32,
}

fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // p1 is copied to p2
// Both p1 and p2 are valid and can be used
}

Clone

For types that need to perform a deep copy involving allocations, define a clone method that returns the duplicated value. The standard library types String and Array<T> both provide clone.

import std.builtin;

fn main() -> void! {
let a = try String.owned("hello");
let b = try a.clone(); // deep copy — new allocation
// a and b are independent instances
}

For your own heap-owning structs, use Allocator and mem.copy to perform the allocation inside clone:

import std.builtin;
import std.mem;

struct Buffer {
@private data: *u8,
@private size: usize,
@private allocator: Allocator,

init(self, size: usize) {
self.size = size;
self.allocator = Allocator.default();
self.data = self.allocator.alloc(size, 1);
}

fn clone(self) -> Buffer! {
let copy = Buffer(self.size);
if (copy.data == null) {
return AllocError.OutOfMemory;
}
mem.copy(copy.data, self.data, self.size);
return copy;
}

fn drop(self) {
if (self.data != null) {
self.allocator.free(self.data, self.size, 1);
}
}
}

Note that clone returns a fallible type (Buffer!) because the allocation may fail.