Zig Data Types
Every value in Zig has a type. The type tells Zig how much memory the value needs, what operations are valid on it, and how to interpret the raw bits stored in that memory. Zig's type system is explicit — you always know the type of every value, either because you wrote it or because Zig inferred it and you can look it up.
Integer Types
Integers hold whole numbers — no decimal point. Zig lets you choose exactly how large an integer is and whether it can be negative.
Signed integers (can be negative): i8 → -128 to 127 i16 → -32,768 to 32,767 i32 → -2 billion to 2 billion (approx) i64 → very large negative to very large positive i128 → astronomically large range Unsigned integers (zero and positive only): u8 → 0 to 255 u16 → 0 to 65,535 u32 → 0 to ~4 billion u64 → 0 to ~18 quintillion u128 → enormous positive range
Choosing the Right Integer Size
Question: What are you storing?
|
+---------+---------+----------+
| | | |
Age Score File size Pixel color
(0-150) (any) (bytes) (0-255)
| | | |
u8 i32 u64 u8
Pick the smallest type that fits your data. A person's age never exceeds 150, so u8 works perfectly and uses only 1 byte. Using i64 for an age wastes 7 bytes per value — harmless for one value, significant for a list of a million users.
Special Integer Types
Zig also provides usize and isize. These match the pointer size of the machine — 32 bits on 32-bit systems, 64 bits on 64-bit systems. Use usize for array indices and memory sizes, since these types guarantee compatibility with the machine's addressing capability.
Floating-Point Types
Floating-point numbers store values with a decimal point.
f16 → Less precision, smaller range (half precision) f32 → Single precision (about 7 decimal digits of accuracy) f64 → Double precision (about 15 decimal digits) ← Default f128 → Quad precision (very high accuracy, slower)
const pi: f64 = 3.14159265358979; const temperature: f32 = 36.6; const tiny: f16 = 0.001;
Use f64 for most calculations. Use f32 when memory is tight (common in graphics and embedded systems). Avoid f16 unless you specifically need it — precision is limited.
Boolean Type
A boolean holds exactly one of two values: true or false.
const is_logged_in: bool = true; const has_errors: bool = false;
Real-world analogy: Light switch: +---------+ | ON | ← true +---------+ +---------+ | OFF | ← false +---------+
Booleans drive decisions in your program. If statements, loops, and conditions all work with boolean values.
Character and String Types
Single Characters
A single character uses the type u8. Characters in Zig are just numbers — each character maps to a number in the ASCII or UTF-8 standard.
const letter: u8 = 'A'; // 'A' is stored internally as the number 65
Strings
Strings in Zig are sequences of bytes — specifically, a pointer to an array of u8 values followed by a zero terminator.
const greeting = "Hello";
Memory layout:
[ H ][ e ][ l ][ l ][ o ][ 0 ]
72 101 108 108 111 0
^
Null terminator
(marks end of string)
The type of a string literal like "Hello" is *const [5:0]u8 — a pointer to a constant array of 5 bytes terminated by zero. This looks complex but the compiler manages it for you in most cases. When writing functions that accept text, use the type []const u8, which is a slice — a pointer plus a length.
Comptime Integers and Floats
const x = 42; // Type: comptime_int const y = 3.14; // Type: comptime_float
When Zig infers the type of a number literal without a type annotation, it creates a comptime value. These values exist only during compilation and have arbitrary precision. They convert automatically to whichever concrete type you use them with. This is why you can write const n: u8 = 42 — the comptime_int value 42 fits in a u8, so Zig accepts it.
The void Type
void means "no value." Functions that return nothing have the return type void. You cannot store a void value in a variable because there is nothing to store.
fn greet() void {
// Does work but returns nothing
}
Type Casting
Zig does not automatically convert between numeric types. You must cast explicitly using @as or casting functions:
const a: i32 = 100; const b: i64 = @as(i64, a); // Explicit widening cast const big: i32 = 300; const small: u8 = @truncate(big); // Truncates to 44 (300 mod 256)
Automatic (allowed) | Requires explicit cast -----------------------|--------------------------- comptime_int → i32 | i64 → i32 comptime_float → f64 | f64 → f32 (literal fits) | i32 → u8 (may truncate)
This strictness prevents subtle bugs where a large number silently gets cut down to a smaller type. In Zig, data loss always requires your explicit consent.
Checking Types at Compile Time
Use the built-in @TypeOf to discover the type of any expression:
const std = @import("std");
pub fn main() void {
const x = 42;
const y = 3.14;
std.debug.print("x is {}\n", .{@TypeOf(x)});
std.debug.print("y is {}\n", .{@TypeOf(y)});
}
Output:
x is comptime_int y is comptime_float
This tool helps while learning — when you are unsure what type Zig inferred, print it and see.
Type Summary Table
Category | Examples | Use For --------------|-------------------|------------------------- Signed int | i8, i32, i64 | Numbers that go negative Unsigned int | u8, u32, u64 | Zero and positive only Float | f32, f64 | Decimal numbers Boolean | bool | True/false decisions Character | u8 with 'A' | Single characters String | []const u8 | Text sequences Void | void | No return value
