Learning objectives and prerequisites
Zig is a general-purpose systems programming language emphasizing explicit behavior and readable control over memory and data. After completing OSZig.001 — What Is Zig?, learners should be able to explain immutable versus mutable bindings, declare a variable with or without an explicit type, recognize common primitive types, and test a small state update. Examples target Zig 0.17.1, released in November 2026. Install the matching compiler using the official Zig downloads if you wish to reproduce the lab.
Elementary overview
A program needs names for information: a rack identifier, the maximum number of servers allowed, or a count of online devices. A Zig variable declaration connects such a name to a value. The distinction begins with the two keywords const and var. A const binding cannot be reassigned after initialization. A var binding can be updated, and Zig expects the programmer to actually need mutability. Zig recommends const by default because the intent is clearer to readers and the compiler.
The declaration syntax follows a predictable pattern: keyword, identifier, optional type, equals sign, value, and semicolon. Begin with a rack label and two numerical variables:
const rack_id = "rack-A7";
const capacity: u32 = 8;
var active: u32 = 3;
active += 1;
The rack ID and capacity remain fixed. The active-device count changes from three to four. Here u32 means an unsigned 32-bit integer, useful for counts that cannot be negative. The arithmetic update += adds one and stores the result in the same mutable location.
Constants, variable names, and reassignment
A const declaration expresses a promise that the binding’s value will not be reassigned. A failed reassignment is caught at compile time. The following minimal example is intentionally invalid, and is useful as a controlled learning exercise rather than a program to run in production:
pub fn main() void {
const capacity: u32 = 8;
capacity = 9; // compile error: cannot assign to constant
}
Use meaningful identifiers such as capacity or active_devices instead of cryptic one-letter names in operational code. Zig identifiers cannot silently shadow an identifier from an outer scope, avoiding a common source of confusion. The earlier C# variables and types lesson provides a comparison with a different set of assignment and type rules.
Mutable variables and the no-change compiler diagnostic
Declare a mutable binding with var only when its value must change. The official Zig reference permits ordinary reassignment and operator assignment. The following sequence starts at zero, increases to one, then increases again to four:
var completed: u32 = 0;
completed += 1;
completed += 3;
If a local variable is declared with var but never actually changed, modern Zig versions generally report that the local variable is never mutated and recommend const. That compiler diagnostic encourages more precise code. Mutability is not a synonym for uncertainty: a value can be unknown until runtime and still be stored under a constant binding.
Primitive types and type inference
Zig supports integers with explicit signedness and widths, floating-point numbers, booleans and byte-oriented string values. Types such as i32 and u32 specify signed and unsigned 32-bit integers; f64 specifies a floating-point format, and bool allows true and false. A string literal can be coerced to a read-only byte slice such as []const u8. The example declares all four explicitly:
const temperature_delta: i32 = -2;
const utilization: f64 = 0.75;
const enabled: bool = true;
const machine_label: []const u8 = "rack-A7";
An untyped numeric literal has a compile-time numeric type until Zig has enough context to give it a concrete integer or floating-point type. An explicit declaration such as const n: i32 = 42; removes the ambiguity. Zig does not automatically treat a string as a number; a conversion must be justified by the destination type and program requirements.
Compile-time versus runtime values
Zig distinguishes values known during compilation from values determined while a program runs. A literal such as 42 can be evaluated at compile time. But a const declaration does not guarantee that its value is available during compilation; it only promises no reassignment. Conversely, a var can be evaluated by compile-time code using comptime. The lesson stays focused on ordinary local declarations, leaving deeper compile-time programming for later study.
The following declarations make the type explicit and create a mutable counter that can change at runtime:
const expected: u32 = 4;
var observed: u32 = 3;
observed += 1;
The final observed value is four. The type annotation prevents accidentally using a negative integer as an unsigned count. When choosing widths, remember that u8 holds 0 through 255 while i8 holds −128 through 127. Overflow is a correctness issue, and programmers should never assume an out-of-range value is harmless.
Blocks, scope, and identifier rules
Local Zig variables live inside functions or other blocks. A nested block can access an appropriate outer variable, but Zig forbids declaring a second variable that shadows an identifier from an enclosing scope. That restriction makes it harder to mistake an inner variable for a different outer variable. Compare the approach with PowerShell’s local and script scopes.
The block below changes the existing outer variable rather than creating another variable with the same name:
pub fn main() void {
var active: u32 = 3;
{
active += 1;
}
_ = active;
}
The underscore discards an evaluated value, avoiding an unused-value diagnostic. In a real program, the updated count would normally be returned, checked or printed. An illegal nested declaration of another active would be rejected because the name is already in scope.
Why initializing variables matters
Zig requires declarations to have an initializer, whether a meaningful value or the special token undefined. The latter is not a safe optional value. Reading an undefined value can cause illegal behavior, and debug memory patterns are not a contract that makes such data meaningful. A beginner should use a valid initial value whenever possible. For example, a count starts at zero rather than being left unreadable:
var completed: u32 = 0;
completed += 1;
A realistic application may use an optional type when a value might be absent; optionals are covered in a later track. If a counter has not yet been collected from monitoring software, a placeholder zero can also be misleading, so the program’s actual requirements should determine how absence is represented.
Practical lab — an AI rack capacity report
An infrastructure example connects immutable settings and mutable operational state. A rack capacity stays fixed for the demonstration, while the active device count increases as another device comes online. Save the following complete source as main.zig using a UTF-8 editor. The program reads no private information, makes no network connection, and requires no elevated permissions:
const std = @import("std");
pub fn main() void {
const label: []const u8 = "rack-A7";
const capacity: u32 = 8;
var active: u32 = 3;
const enabled: bool = true;
active += 1;
std.debug.print(
"rack={s} active={d}/{d} enabled={}\n",
.{ label, active, capacity, enabled },
);
}
Under Zig 0.17.1 semantics, the expected message is rack=rack-A7 active=4/8 enabled=true. std.debug.print normally writes to the standard error stream, so a shell redirection that captures only standard output may miss the message. The sample has been source-reviewed against Zig’s current language reference; the Zig compiler could not be installed in the publication environment for independent execution.
Run the Zig compiler from the directory containing the file. The same source can be compiled directly or run using Zig’s combined build-and-execute command:
zig version
zig run main.zig
Confirm that the reported compiler is 0.17.1 when reproducing the lesson’s exact version. If your machine has a different version, refer to its own language reference and verify any API changes before adopting code from another release.
Testing the expected result
The Zig toolchain also includes a test runner. A test declaration exercises a small behavior without adding complexity to main. Create a separate file named counter_test.zig containing one test for a counter increment and its capacity limit:
const std = @import("std");
test "active devices remain within capacity" {
const capacity: u32 = 8;
var active: u32 = 3;
active += 1;
try std.testing.expectEqual(@as(u32, 4), active);
try std.testing.expect(active <= capacity);
}
The expected result is one passing test. Run the test file with the toolchain rather than copying output from an example online:
zig test counter_test.zig
The compiler should reject incompatible types and invalid reassignment before execution. Because the Zig 0.17.1 binary was unavailable in the current execution environment, the expected outputs above remain source-reviewed predictions, not independently recorded compiler or terminal results. Testing on the learner’s own system should be treated as the final validation step.
Troubleshooting common mistakes
A cannot assign to constant error means code attempted to modify a const binding. Use var only when genuine updates are required. A local variable is never mutated diagnostic usually means const would better describe the intended value. A type mismatch means the expression does not match the declared destination type. A missing semicolon or unclosed brace may prevent the parser from reaching any variable analysis.
When terminal output does not appear, remember that std.debug.print uses standard error rather than standard output. Check the active Zig version, exact file path and editor encoding before assuming the language code is incorrect. Use test cases to verify counts and ranges instead of relying exclusively on visual inspection.
Knowledge check and answers
What is the difference between const and var? A constant binding cannot be reassigned, while a mutable binding may change. What does u32 mean? An unsigned 32-bit integer. Why does Zig reject some unused var declarations? It encourages immutable bindings when no mutation occurs. Why is undefined risky? It represents data without a meaningful value and must not be read before proper assignment. Why does zig test matter? It helps verify the declared behavior against actual compiler and runtime results.
For independent practice, change the capacity from eight to twelve, start active devices at ten and increment twice. Update the test’s expected value to twelve, then add an assertion that the total does not exceed capacity. Finally, replace an unnecessarily mutable binding with const and explain why the program becomes easier to review. Comparable topics appear in Rust fundamentals and C# immutability and strings, though Zig’s compiler behavior is distinct.
Primary documentation and further reading
Zig 0.17.1 Language Reference documents values, assignment, local variables, scopes and compiler diagnostics. The official Zig download page identifies the stable release, while the November 3 release announcement explains the bug-fix update. Zig’s Learn page links to verified setup guides, exercises and the Ziglings curriculum.
Elementary conclusion
Zig makes mutability explicit. Use const for values that will not be reassigned, var for values that genuinely change, and explicit numeric types when their width or signedness matters. A compiler-checked variable model prevents several classes of accidental modification before a program runs. The next Zig lessons can build on the same foundation when introducing functions, arrays, slices and pointers.
Editor’s Note: The lesson is based on Zig 0.17.1, the latest stable version as of November 5, 2026. Examples were reviewed against the official Zig language reference, but could not be compiled or executed in the available environment. The featured image is original conceptual editorial artwork rather than a screenshot of a verified runtime session.

For mobile scanning, open the lesson on another display and use a compatible Bitcoin wallet to scan the original QR code. Verify the destination before donating 5,000 sats: 3C9o19EH5HSiwEPyCTmEKzxhNCbo2X6TTb.
We volunteer daily to ensure the credibility of the information on this platform is Verifiably True. If you would like to support our work and help further secure the integrity of our research initiatives, please scan the QR code below to donate 5,000 sats.
BitcoinVersus.tech is not a financial advisor. This media platform reports on financial subjects purely for informational purposes.

Leave a Reply