Data Types & Literals
Explicit-Width Integers
Unlike most languages, Zig does not hide the hardware: you choose the exact number of bits. The compiler then rejects operations that could overflow in safe builds, which is a gift — silent overflow is a classic source of security bugs.
Integer Types
| Family | Types |
|---|---|
| Unsigned | u8 u16 u32 u64 u128 u256 usize |
| Signed | i8 i16 i32 i64 i128 isize |
| Architecture | usize / isize are pointer-sized; any width u1..u65535 is expressible |
| Compile-time | comptime_int, comptime_float — seen only in the compiler |
Any bit width 1..=65535 is expressible (u7, i9, ...); common widths are just shorthands. Prefer u8/u32/u64 in practice and reserve exotic widths for packet formats and bit-level work.
Overflow Behavior
var a: u8 = 200;
const wrapped = a +% 100; // 44 — wrapping adds modulo 256, explicit
const bounded = a +| 100; // 255 — saturating stops at the maximum
const boom = a + 100; // panic in Debug/ReleaseSafe: integer overflow
The plain + is checked; the exotic operators +% (wrapping) and +| (saturating) are deliberate escapes used only where the semantics truly need them.
Floating Point
Floats follow IEEE-754 exactly. There is no implicit integer-to-float trap: widening from integer to float is allowed implicitly, but narrowing a float into an integer always requires an explicit cast, because the result is lossy.
Float Types
f16— half precision, GPU and storagef32— single precision, the default for most mathf64— double precision, financial and scientific workf128— quad precision, rare and slow
Precision Pitfalls
const a: f32 = 0.1 + 0.2;
// 0.300000011920929... — IEEE-754 cannot represent 0.1 exactly
// Never compare floats with ==; use an epsilon or std.math.approxEqAbs
Booleans
A bool is exactly one value: true or false. Zig uses readable keyword operators and short-circuit evaluation (the right side runs only when needed).
Logical Operators
const ok: bool = true;
const done = ok and (10 > 5); // true — and short-circuits
const skip = done or foo(); // true — foo() is NEVER called (short-circuit)
const neg = !done; // false
Arrays
Arrays have a length known at compile time, written in the type: [N]T. Indexing is bounds-checked in safe builds — an out-of-range access is caught, not silent.
Array Literals
const fib = [_]u32{ 1, 1, 2, 3, 5 };
// [_] infers the length: [5]u32
const second = fib[1]; // 1 — checked at runtime in safe builds
const abc = [_]u8{ a, b, c };
// Array multiplication repeats a small array: "abcabcabc" == (_*3)
Vectors
Vectors are explicit SIMD types: @Vector(4, f32). They behave like arrays but map to CPU vector instructions when you target them with arithmetic. Use them only after profiling shows a hot loop worth vectorizing.
Compile-Time Literals
An integer literal without a declared type is a comptime_int: an exact value that does not yet exist in memory. It coerces automatically wherever a concrete integer type can hold it, which allows expressions like the array length above without declaring a type first.
Safe Coercion
const n: u32 = 40 + 2; // comptime_int -> u32, holds fine
const big: u64 = n * n; // promoted, also fine
const b: u8 = 300; // COMPILE ERROR: comptime_int 300 does not fit u8
const casted: u8 = @intCast(300); // explicit, allowed only where lossless or deliberate
@intCast is the escape hatch; every use is visible in the source, and the runtime checks it in safe builds.
Common Pitfalls
Wrong Integer Width
Choosing i32 when u8 fits wastes memory and invites overflow bugs. Choose by range, then by hardware preference (u32/u64 are native on most CPUs).
Forgetting Overflow Semantics
Reading + as always-overflowing is wrong in Zig: plain operators are checked, and +%/+| are the explicit escapes. The choice is visible to anyone reviewing the code.
Next: Control Flow — if, switch, and loops as expressions.