Statements & Expressions
@ so you always recognize a builtin.
Statements and Expressions
An expression produces a value; a statement performs an action. Zig deliberately blurs the line: control-flow constructs such as if and switch are expressions that return values, which removes a classic bug source — the forgotten intermediate variable.
Statement Terminators
Every statement ends with a semicolon, and Zig never inserts one for you. A missing semicolon is a compile error with the exact line number, not a silent behavior change.
const answer: u32 = 42; // declaration statement
std.debug.print("hi\n", .{}); // call statement
_ = answer; // discard statement on purpose
Blocks
Braces group statements and introduce a scope. A block is itself an expression: when its last statement is a value, the block evaluates to it, and a label lets you break out of the block with that value.
const total = blk: {
const a: u32 = 2;
const b: u32 = 3;
break :blk a + b; // the block "returns" this value
};
// total == 5
Identifiers
Identifiers begin with a letter or underscore, then continue with letters, digits, or underscores. Convention is snake_case for values and functions and PascalCase for types; the compiler is case-sensitive, so answer and Answer are different names.
The @ Family
Names beginning with @ are reserved for the compiler. Builtins such as @intCast, @sizeOf, and @import cannot be shadowed, so their meaning is always unambiguous in code.
const len = @sizeOf(u32); // 4 — the builtin knows the type's size
const imp = @import("std"); // the import builtin loads another module
const cast = @as(u32, 7); // explicit, checked type coercion
Variable Declarations
Zig has two declaration keywords — const and var — plus the special value undefined for declaring an uninitialized slot. The distinction is core to "thinking in Zig": const values are immutable and the compiler can often prove their whole life, so prefer them.
const versus var
const pi: f64 = 3.14159; // immutable — the value never changes
var counter: u32 = 0; // mutable — reassigned with an assignment
counter += 1;
var buffer: [16]u8 = undefined; // declared, contents not initialized yet
Type Inference
When the type is obvious from the initializer you can omit it, and inference remains strongly typed — there is no fallback to a generic "number". Integers and floats without a declared type are bootstrapped as compile-time values, which you will meet in the types lesson.
const name = "Sage-Code"; // []const u8 inferred — a slice of bytes
const count = 10; // comptime_int inferred
const ratio = 0.5; // comptime_float inferred
Operators
Zig keeps C's familiar operator set and adds explicit wrapped arithmetic (+%, -%, *%) that is allowed to overflow, saturating variants (+|, -|, *|), and keyword logical operators and/or instead of &&/||.
Operator Table
| Category | Operators | Example |
|---|---|---|
| Arithmetic | + - * / % | a + b |
| Wrapped | +% -% *% | a +% b |
| Saturating | +| -| *| | a +| b |
| Comparison | == != < > <= >= | a < b |
| Logical | and or ! | a and b |
| Bitwise | & | ^ ~ << >> | a & b |
| Assignment | = += -= *= /= %= &= |= ^= <<= >>= | a += 1 |
| Concatenation | ++ (arrays) ** (repetition) | a ++ b |
Operator precedence matches C: * before +, comparison before logical, assignment lowest. When in doubt, add parentheses — clarity beats cleverness.
Comments
Comments document intent for humans and are ignored by the compiler. Zig keeps the two C-style kinds and adds doc comments that tooling and IDEs can display.
Comment Types
// single-line comment
/* multi-line
comment */
/// doc comment — attached to the next declaration
//! file-level doc comment at the top of the file
Write why in comments, not what the code plainly shows. The code tells the compiler what happens; the comment tells the next engineer why it should happen this way.
Common Pitfalls
Three mistakes trap every newcomer; knowing them in advance saves your first hour with the compiler.
Missing Semicolons
Unlike JavaScript, Zig never inserts semicolons. Every declaration, assignment, and call needs its terminator; the error message points at the exact line.
Shadowing Edge Cases
Zig allows inner scopes to reuse a name, but accidental shadowing of a value you still need later is a real debugging cost. Keep names unique inside a function and let zig fmt enforce consistent style.
Unused Values
The compiler reports unused local variables and return values. Assign to _ to discard deliberately — visibility into waste is a feature of Zig, not noise.
_ = someFunction(); // I really meant to ignore this result
Next: Data Types & Literals — the integer, float, and array building blocks.