@memcpy
@memcpy copies bytes from one memory region to another.
The name means “memory copy.”
You use it when you already have destination memory and source memory, and you want the destination to receive the source bytes.
@memcpy(destination, source);The destination and source are usually slices.
A Simple Example
const std = @import("std");
pub fn main() void {
var dst = [_]u8{ 0, 0, 0, 0 };
const src = [_]u8{ 1, 2, 3, 4 };
@memcpy(&dst, &src);
std.debug.print("{any}\n", .{dst});
}Output:
{ 1, 2, 3, 4 }The bytes from src were copied into dst.
Destination Must Be Mutable
The destination must allow writing.
This works:
var dst = [_]u8{ 0, 0, 0 };
const src = [_]u8{ 1, 2, 3 };
@memcpy(&dst, &src);This does not work:
const dst = [_]u8{ 0, 0, 0 };
const src = [_]u8{ 1, 2, 3 };
@memcpy(&dst, &src);The destination is const, so Zig will not let you write into it.
Source and Destination Lengths Must Match
For slices, the destination and source must have the same length.
var dst = [_]u8{ 0, 0, 0 };
const src = [_]u8{ 1, 2, 3 };
@memcpy(&dst, &src);Both have length 3.
This is wrong:
var dst = [_]u8{ 0, 0, 0 };
const src = [_]u8{ 1, 2, 3, 4 };
@memcpy(&dst, &src);The destination has 3 bytes. The source has 4 bytes. Zig rejects this or detects the mismatch.
For dynamic slices, make the lengths explicit:
@memcpy(dst[0..src.len], src);This says: copy exactly src.len items.
Copying Part of a Buffer
Suppose you have a larger buffer:
var buffer = [_]u8{0} ** 16;
const hello = "hello";You can copy the string into the first part of the buffer:
@memcpy(buffer[0..hello.len], hello);Full example:
const std = @import("std");
pub fn main() void {
var buffer = [_]u8{0} ** 16;
const text = "hello";
@memcpy(buffer[0..text.len], text);
std.debug.print("{s}\n", .{buffer[0..text.len]});
}Output:
helloThe buffer has 16 bytes, but only the first 5 bytes receive the text.
@memcpy Copies Elements
Although the name says “memory,” @memcpy works with element slices.
For byte slices, it copies bytes.
For u32 slices, it copies u32 elements.
var dst = [_]u32{ 0, 0, 0 };
const src = [_]u32{ 10, 20, 30 };
@memcpy(&dst, &src);This copies 3 u32 values.
The total number of copied bytes is:
3 * @sizeOf(u32)So on common targets, that is:
3 * 4 = 12 bytesBut you usually think of it as copying three elements.
Source and Destination Must Not Overlap
@memcpy is for non-overlapping memory.
This is important.
If the source and destination refer to overlapping parts of the same buffer, the result is not safe to rely on.
Example of overlapping memory:
var data = [_]u8{ 1, 2, 3, 4, 5 };
@memcpy(data[1..5], data[0..4]);The source is:
data[0..4] = 1, 2, 3, 4The destination is:
data[1..5] = 2, 3, 4, 5These ranges overlap.
Use the standard library helper for overlapping copies when needed, such as std.mem.copyForwards or std.mem.copyBackwards, depending on direction.
@memcpy vs Assignment
For fixed-size arrays, simple assignment may be clearer.
var a = [_]u8{ 1, 2, 3 };
var b = [_]u8{ 0, 0, 0 };
b = a;This copies the array value.
For slices and buffers, @memcpy is useful because you are copying into existing memory.
@memcpy(dst, src);A good rule:
Use assignment for whole values.
Use @memcpy for copying into a memory region.
@memcpy Does Not Allocate
@memcpy only copies into memory that already exists.
This is wrong thinking:
var dst: []u8 = undefined;
@memcpy(dst, "hello");The slice dst does not own valid memory here. It has no real buffer.
You need actual storage:
var buffer = [_]u8{0} ** 5;
@memcpy(&buffer, "hello");Or you need allocated memory:
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const src = "hello";
const dst = try allocator.alloc(u8, src.len);
defer allocator.free(dst);
@memcpy(dst, src);
}The allocator creates memory. @memcpy fills it.
Copying Struct Bytes
Sometimes you want to copy the raw bytes of a value.
For ordinary Zig code, prefer normal assignment:
const Point = struct {
x: i32,
y: i32,
};
var a = Point{ .x = 1, .y = 2 };
var b = a;This is clear and type-safe.
Only use byte-level copying when you are working with representation-level code, such as serialization, binary formats, or low-level APIs.
@memcpy Is Low-Level
@memcpy is not complicated, but it is low-level.
It assumes you already know:
the destination memory is valid
the destination is writable
the source memory is valid
the lengths match
the regions do not overlap
Zig checks what it can, but your design still matters.
A Common Pattern
This pattern appears often:
fn writePrefix(buffer: []u8, prefix: []const u8) void {
@memcpy(buffer[0..prefix.len], prefix);
}But this function has a hidden assumption: buffer.len >= prefix.len.
A safer version checks it:
fn writePrefix(buffer: []u8, prefix: []const u8) !void {
if (buffer.len < prefix.len) return error.BufferTooSmall;
@memcpy(buffer[0..prefix.len], prefix);
}Now the function says what can go wrong.
Key Idea
@memcpy(destination, source) copies memory from source into destination.
It does not allocate.
It does not resize.
It should not be used for overlapping regions.
Use it when you already have valid destination memory and want to copy elements from a source slice into it.