Variables and Types in Zig
Learn about variables, primitive types, integer bit widths, optionals, and explicit type conversions in Zig with practical Docker-ready examples
Zig is a systems programming language with a static, strong type system that prefers being explicit over being clever. Every binding is either const (immutable) or var (mutable), every numeric conversion between types must be spelled out with a built-in, and “maybe a value, maybe nothing” is encoded directly into the type with the ? optional marker rather than left to runtime convention.
This tutorial introduces Zig’s primitive types — including its unusual ability to declare integers of any bit width — along with type inference, mutability rules, and the explicit cast built-ins that distinguish Zig from C. By the end you’ll know how to declare bindings, pick appropriate numeric types, and convert between them safely.
Declaring Bindings: const vs var
Zig has two keywords for introducing a name:
const— the binding cannot be reassigned. Prefer this; the compiler will tell you to useconstif avaris never mutated.var— the binding is mutable. Requires a type annotation or an initializer the compiler can infer from.
Type annotations are written after a colon (name: Type = value). They are optional when the compiler can infer the type from the right-hand side.
Create a file named variables.zig:
| |
A few things worth noting:
u3is a real type. Zig lets you declare integers of arbitrary bit widths fromu0/i0up tou65535/i65535. The compiler enforces the range — assigning8to au3is a compile error.usizeandisizeare pointer-sized integers (used for array lengths and indexes), analogous tosize_tin C.- String literals like
"Zig"have type[]const u8— a slice of constant bytes. Zig doesn’t have a separateStringtype at this level. - The underscore separator (
9_000_000_000) is purely cosmetic and works in all numeric literals.
Type Conversions and Optionals
Zig requires every numeric conversion between types to be explicit via a built-in function (the ones prefixed with @). There is no implicit widening, no implicit narrowing, and no surprise truncation. This eliminates an entire category of C bugs.
Optionals are Zig’s answer to “this might be missing.” Prefixing a type with ? produces a new type that can hold either a value of that type or null.
Create a file named conversions.zig:
| |
Key cast built-ins to remember:
@intCast— convert between integer types of different widths.@floatCast— convert between float types.@floatFromInt/@intFromFloat— convert across the int/float boundary.@as(T, value)— coerce a value to a specific type when inference isn’t enough.
In ReleaseSafe and Debug builds, narrowing casts that would lose data (e.g. assigning 70_000 to a u16) trap at runtime. In ReleaseFast they are undefined behavior — Zig’s safety is opt-in by build mode.
Running with Docker
| |
Expected Output
Running variables.zig:
language = Zig
year = 2025
is_systems_lang = true
answer = 42
byte_value (u8) = 255
big_int (i64) = 9000000000
three_bits (u3) = 7
Running conversions.zig:
before assignment: null
after assignment: Andrew
pi = 3.14
int -> float = 42.0
float -> int = 9
i64 1000 -> i16 = 1000
unwrapped name = Andrew
Key Concepts
constis the default — Zig nudges you toward immutable bindings. Usevaronly when you actually need to reassign, or the compiler will complain.- Arbitrary-width integers — types like
u3,i7, oru128are first-class. Pick the smallest type that fits the data; the compiler enforces the range. - No implicit numeric conversions — every cast is spelled out with a built-in (
@intCast,@floatFromInt,@as, etc.). This is more typing but eliminates silent overflow and truncation bugs. - Optionals replace null pointers —
?Tis a distinct type fromT. You cannot accidentally use a null where a value is required; you must unwrap withorelse,if (opt) |v|, or.?. - Strings are byte slices —
[]const u8is the idiomatic string type. There is no separateStringclass with hidden allocations. - Safety depends on build mode — Debug and ReleaseSafe trap on out-of-range casts and integer overflow; ReleaseFast and ReleaseSmall trade those checks for speed.
- Underscores in literals — write
1_000_000or0xFF_FFto keep large numbers readable; the compiler ignores them.
Running Today
All examples can be run using Docker:
docker pull kassany/alpine-ziglang:0.14.0
Comments
Loading comments...
Leave a Comment