@sizeOf
@sizeOf asks the Zig compiler how many bytes a type needs in memory.
Example:
const n = @sizeOf(u32);For u32, the result is 4, because a 32-bit unsigned integer uses 4 bytes.
A byte is 8 bits. So:
u8 = 1 byte
u16 = 2 bytes
u32 = 4 bytes
u64 = 8 bytesThe name @sizeOf means “size of this type.”
@sizeOf Takes a Type
@sizeOf does not ask for the size of a value directly. It asks for the size of a type.
This is correct:
const a = @sizeOf(u64);This is also common:
const x: i32 = 123;
const b = @sizeOf(@TypeOf(x));Here, @TypeOf(x) gets the type of x, which is i32. Then @sizeOf(i32) returns the size of that type.
You can read it like this:
What is the type of x?
What is the size of that type?A Simple Program
const std = @import("std");
pub fn main() void {
std.debug.print("u8: {} byte\n", .{@sizeOf(u8)});
std.debug.print("u16: {} bytes\n", .{@sizeOf(u16)});
std.debug.print("u32: {} bytes\n", .{@sizeOf(u32)});
std.debug.print("u64: {} bytes\n", .{@sizeOf(u64)});
}Possible output:
u8: 1 byte
u16: 2 bytes
u32: 4 bytes
u64: 8 bytesThese sizes matter because Zig is a systems programming language. You often need to know exactly how much memory your data uses.
Why Size Matters
Memory is not abstract in Zig. Your data has a real layout.
If you store one million u64 values, each value uses 8 bytes.
So the total memory for the raw values is:
1,000,000 * 8 = 8,000,000 bytesThat is about 8 megabytes, before considering allocator overhead or container metadata.
If you used u32 instead:
1,000,000 * 4 = 4,000,000 bytesThat is about 4 megabytes.
Choosing the right type can cut memory use in half.
Struct Sizes
@sizeOf is especially useful with structs.
Example:
const Point = struct {
x: i32,
y: i32,
};
const size = @sizeOf(Point);Each i32 uses 4 bytes. The struct has two of them. So Point is usually 8 bytes.
const std = @import("std");
const Point = struct {
x: i32,
y: i32,
};
pub fn main() void {
std.debug.print("Point size: {} bytes\n", .{@sizeOf(Point)});
}Possible output:
Point size: 8 bytesPadding
Struct size is not always just the sum of field sizes.
The compiler may add padding bytes to keep fields aligned correctly.
Example:
const Example = struct {
a: u8,
b: u32,
};u8 is 1 byte. u32 is 4 bytes.
A beginner may expect:
1 + 4 = 5 bytesBut the actual size is usually larger, often 8 bytes.
Why? Because u32 usually wants to start at an address aligned to 4 bytes. The compiler may insert 3 padding bytes after a.
The memory layout may look like this:
byte 0: a
byte 1: padding
byte 2: padding
byte 3: padding
byte 4: b
byte 5: b
byte 6: b
byte 7: bSo:
const Example = struct {
a: u8,
b: u32,
};
const size = @sizeOf(Example); // often 8Padding is normal. It helps the CPU read values efficiently.
Field Order Can Change Size
The order of fields can affect struct size.
Compare these two structs:
const A = struct {
a: u8,
b: u32,
c: u8,
};and:
const B = struct {
b: u32,
a: u8,
c: u8,
};They store the same logical data, but their memory layout may differ.
A may need more padding than B.
You can inspect this:
const std = @import("std");
const A = struct {
a: u8,
b: u32,
c: u8,
};
const B = struct {
b: u32,
a: u8,
c: u8,
};
pub fn main() void {
std.debug.print("A: {} bytes\n", .{@sizeOf(A)});
std.debug.print("B: {} bytes\n", .{@sizeOf(B)});
}Possible output:
A: 12 bytes
B: 8 bytesThis is one reason systems programmers care about layout.
Arrays
For arrays, @sizeOf includes every element.
const Numbers = [4]u32;
const size = @sizeOf(Numbers);A u32 is 4 bytes. The array has 4 elements.
So the size is:
4 * 4 = 16 bytesExample:
const std = @import("std");
pub fn main() void {
std.debug.print("[4]u32 size: {} bytes\n", .{@sizeOf([4]u32)});
}Output:
[4]u32 size: 16 bytesA fixed array stores its elements directly. The array value contains the actual elements.
Slices
A slice is different from an array.
A slice points to existing memory and stores a length.
Example type:
[]u8A slice does not contain all the bytes directly. It contains:
pointer to data
lengthSo @sizeOf([]u8) gives the size of the slice header, not the size of the data it points to.
On a 64-bit target, this is commonly 16 bytes:
8-byte pointer + 8-byte length = 16 bytesExample:
const std = @import("std");
pub fn main() void {
std.debug.print("[]u8 size: {} bytes\n", .{@sizeOf([]u8)});
}Possible output on a 64-bit system:
[]u8 size: 16 bytesThis does not mean the string or buffer has only 16 bytes of content. It means the slice value itself is 16 bytes.
Pointers
Pointer sizes depend on the target.
On a 64-bit target, a pointer is usually 8 bytes.
On a 32-bit target, a pointer is usually 4 bytes.
Example:
const std = @import("std");
pub fn main() void {
std.debug.print("*u8 size: {} bytes\n", .{@sizeOf(*u8)});
}Possible output on a 64-bit system:
*u8 size: 8 bytesThis is why @sizeOf is target-aware. Zig can cross-compile, and some sizes change depending on the target.
usize
The type usize is an unsigned integer large enough to hold a memory address size for the target.
On a 64-bit target:
@sizeOf(usize) = 8On a 32-bit target:
@sizeOf(usize) = 4This is why usize is often used for indexes, lengths, and sizes.
Example:
const std = @import("std");
pub fn main() void {
std.debug.print("usize size: {} bytes\n", .{@sizeOf(usize)});
}Zero-Sized Types
Some types have size 0.
Example:
const Empty = struct {};This struct has no fields. It stores no data.
const std = @import("std");
const Empty = struct {};
pub fn main() void {
std.debug.print("Empty size: {} bytes\n", .{@sizeOf(Empty)});
}Output:
Empty size: 0 bytesZero-sized types are useful in generic code and marker types.
Size vs Length
Do not confuse size with length.
For an array:
const a = [_]u8{ 10, 20, 30 };The length is 3.
The size is 3 bytes, because each u8 is 1 byte.
But for this array:
const b = [_]u32{ 10, 20, 30 };The length is still 3.
The size is 12 bytes, because each u32 is 4 bytes.
So:
length = number of elements
size = number of bytesFor arrays, the relationship is:
size = length * size of each elementA Useful Pattern
You can use @sizeOf when allocating memory, checking formats, or validating assumptions.
Example:
comptime {
if (@sizeOf(u64) != 8) {
@compileError("expected u64 to be 8 bytes");
}
}This check runs at compile time.
For fixed binary formats, this kind of check can prevent subtle bugs.
When Beginners Should Use @sizeOf
Use @sizeOf when you need to know the memory size of a type.
Common cases:
- inspecting structs
- working with binary files
- writing network protocols
- using allocators
- calling C code
- checking pointer and integer sizes
- understanding memory layout
Do not use it to ask how many items are in a slice or array. For that, use .len.
Example:
const xs = [_]u8{ 1, 2, 3 };
const length = xs.len; // 3
const size = @sizeOf(@TypeOf(xs)); // 3For u8, the numbers happen to match. For other types, they may not.
const ys = [_]u32{ 1, 2, 3 };
const length = ys.len; // 3
const size = @sizeOf(@TypeOf(ys)); // 12Key Idea
@sizeOf(T) returns the number of bytes needed to store a value of type T.
It is known at compile time.
It is useful because Zig makes memory layout visible. When you understand size, you understand one of the most basic facts about how your program uses memory.