Statements & Expressions

Zig is a free-form, C-family language: statements end with a semicolon, blocks use braces, and identifiers are case-sensitive. The key difference from C is discipline — no automatic semicolon insertion, no preprocessor, and every compiler magic name starts with an @ 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

CategoryOperatorsExample
Arithmetic+ - * / %a + b
Wrapped+% -% *%a +% b
Saturating+| -| *|a +| b
Comparison== != < > <= >=a < b
Logicaland 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.