Control Flow
Control flow statements let you make decisions and repeat actions in your programs. In this lesson, we'll explore Zig's if statements, loops, and switch expressions.
What You'll Learn
- How to use if/else statements for conditional logic
- Different types of loops: while, for, and labeled loops
- Switch expressions for pattern matching
- How to use break and continue in loops
- Error handling with control flow
If Statements
Zig's if statements are straightforward but powerful:
const std = @import("std");
pub fn main() !void {
const temperature: i32 = 25;
// Basic if statement
if (temperature > 30) {
std.debug.print("It's hot outside!\n", .{});
}
// If-else
if (temperature < 0) {
std.debug.print("It's freezing!\n", .{});
} else {
std.debug.print("Temperature is above freezing\n", .{});
}
// If-else if-else chain
if (temperature < 0) {
std.debug.print("Freezing\n", .{});
} else if (temperature < 10) {
std.debug.print("Cold\n", .{});
} else if (temperature < 20) {
std.debug.print("Mild\n", .{});
} else {
std.debug.print("Warm\n", .{});
}
}
If as an Expression
In Zig, if can be used as an expression to return values:
const std = @import("std");
pub fn main() !void {
const score: i32 = 85;
// If as an expression
const grade = if (score >= 90)
"A"
else if (score >= 80)
"B"
else if (score >= 70)
"C"
else if (score >= 60)
"D"
else
"F";
std.debug.print("Score: {}, Grade: {s}\n", .{score, grade});
// If with optional values
const maybe_value: ?i32 = 42;
if (maybe_value) |value| {
std.debug.print("Value exists: {}\n", .{value});
} else {
std.debug.print("Value is null\n", .{});
}
}
While Loops
While loops continue executing as long as a condition is true:
const std = @import("std");
pub fn main() !void {
// Basic while loop
var count: i32 = 0;
while (count < 5) {
std.debug.print("Count: {}\n", .{count});
count += 1;
}
// While with continue expression
var i: i32 = 0;
while (i < 10) : (i += 1) {
if (i % 2 == 0) {
std.debug.print("{} is even\n", .{i});
}
}
// While with optional (loops until null)
var optional: ?i32 = 5;
while (optional) |value| {
std.debug.print("Value: {}\n", .{value});
optional = if (value > 0) value - 1 else null;
}
}
For Loops
For loops iterate over ranges and slices:
const std = @import("std");
pub fn main() !void {
// Loop over a range
for (0..5) |i| {
std.debug.print("Index: {}\n", .{i});
}
// Loop over an array
const numbers = [_]i32{10, 20, 30, 40, 50};
for (numbers) |num| {
std.debug.print("Number: {}\n", .{num});
}
// Loop with index
const fruits = [_][]const u8{"apple", "banana", "cherry"};
for (fruits, 0..) |fruit, idx| {
std.debug.print("{}: {s}\n", .{idx, fruit});
}
// Loop over a string (slice of bytes)
const message = "Hello";
for (message) |char| {
std.debug.print("{c} ", .{char});
}
std.debug.print("\n", .{});
}
Break and Continue
Control loop execution with break and continue:
const std = @import("std");
pub fn main() !void {
// Using break
var i: i32 = 0;
while (true) {
if (i >= 5) {
break; // Exit the loop
}
std.debug.print("{} ", .{i});
i += 1;
}
std.debug.print("\n", .{});
// Using continue
for (0..10) |num| {
if (num % 2 == 0) {
continue; // Skip even numbers
}
std.debug.print("{} ", .{num}); // Only prints odd numbers
}
std.debug.print("\n", .{});
// Break with value (returning from loop)
const result = for (0..10) |n| {
if (n * n > 50) {
break n; // Return the value
}
} else 0; // Default value if loop completes without break
std.debug.print("First n where n^2 > 50: {}\n", .{result});
// Try modifying these loops!
}
Switch Expressions
Switch is a powerful pattern matching feature in Zig:
const std = @import("std");
pub fn main() !void {
// Basic switch
const day: u8 = 3;
const day_name = switch (day) {
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
4 => "Thursday",
5 => "Friday",
6 => "Saturday",
7 => "Sunday",
else => "Invalid day",
};
std.debug.print("Day {}: {s}\n", .{day, day_name});
// Switch with multiple values
const grade: u8 = 85;
const performance = switch (grade) {
90...100 => "Excellent",
80...89 => "Good",
70...79 => "Average",
60...69 => "Below Average",
else => "Failing",
};
std.debug.print("Grade {}: {s}\n", .{grade, performance});
// Switch with enums
const Color = enum { red, green, blue, yellow };
const color = Color.green;
const hex = switch (color) {
.red => "#FF0000",
.green => "#00FF00",
.blue => "#0000FF",
.yellow => "#FFFF00",
};
std.debug.print("Color {s}: {s}\n", .{@tagName(color), hex});
}
Labeled Blocks and Loops
You can label blocks and loops to control flow more precisely:
const std = @import("std");
pub fn main() !void {
// Labeled break
outer: for (0..5) |i| {
for (0..5) |j| {
if (i * j > 6) {
std.debug.print("Breaking at i={}, j={}\n", .{i, j});
break :outer; // Break out of outer loop
}
std.debug.print("({}, {}) ", .{i, j});
}
std.debug.print("\n", .{});
}
// Labeled continue
outer2: for (0..3) |i| {
for (0..3) |j| {
if (j == 1) {
continue :outer2; // Continue outer loop
}
std.debug.print("({}, {}) ", .{i, j});
}
std.debug.print("\n", .{});
}
// Labeled blocks (return values from blocks)
const result = blk: {
const x = 10;
const y = 20;
break :blk x + y;
};
std.debug.print("Result from block: {}\n", .{result});
}
Error Handling in Control Flow
Combine control flow with error handling:
const std = @import("std");
const ValidationError = error{TooSmall, TooBig};
fn validateAge(age: i32) ValidationError!void {
if (age < 0) {
return ValidationError.TooSmall;
}
if (age > 150) {
return ValidationError.TooBig;
}
}
pub fn main() !void {
const ages = [_]i32{-5, 25, 200, 45};
for (ages) |age| {
// If statement with error handling
if (validateAge(age)) {
std.debug.print("Age {} is valid\n", .{age});
} else |err| {
std.debug.print("Age {} is invalid: {}\n", .{age, err});
}
}
}
Practice Exercises
Try these exercises:
- Write a program that prints numbers 1-100, but for multiples of 3 print "Fizz", for multiples of 5 print "Buzz", and for multiples of both print "FizzBuzz"
- Create a loop that calculates the factorial of a number
- Use a switch statement to convert a month number (1-12) to its name
- Write nested loops to print a multiplication table
- Create a function that finds the first prime number greater than a given number using a loop and break
Challenge: Write a function that determines if a number is prime using loops and conditional statements. Test it with numbers from 2 to 30.
Key Takeaways
- If statements can be used as expressions to return values
- While loops continue until a condition is false; for loops iterate over ranges and slices
- Use
breakto exit loops early andcontinueto skip to the next iteration - Switch expressions provide exhaustive pattern matching
- Labeled blocks and loops allow precise control flow in nested structures
- Control flow works seamlessly with error handling using if-else with errors
Next Steps
Now that you understand control flow, let's explore Zig's data structures including arrays and structs.
Ready to continue? Let's learn about Arrays and Structs!