Skip to content

`@sizeOf`

@sizeOf asks the Zig compiler how many bytes a type needs in memory.

@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 bytes

The 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 bytes

These 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 bytes

That is about 8 megabytes, before considering allocator overhead or container metadata.

If you used u32 instead:

1,000,000 * 4 = 4,000,000 bytes

That 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 bytes

Padding

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 bytes

But 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: b

So:

const Example = struct {
    a: u8,
    b: u32,
};

const size = @sizeOf(Example); // often 8

Padding 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 bytes

This 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 bytes

Example:

const std = @import("std");

pub fn main() void {
    std.debug.print("[4]u32 size: {} bytes\n", .{@sizeOf([4]u32)});
}

Output:

[4]u32 size: 16 bytes

A 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:

[]u8

A slice does not contain all the bytes directly. It contains:

pointer to data
length

So @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 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: 16 bytes

This 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 bytes

This 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) = 8

On a 32-bit target:

@sizeOf(usize) = 4

This 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 bytes

Zero-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 bytes

For arrays, the relationship is:

size = length * size of each element

A 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)); // 3

For 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)); // 12

Key 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.