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
Copycannot also have adropmethod. - All fields in a
Copystruct must also implementCopy.
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.