TL;DR
Master Zig slices and iteration — use fat pointers for safe array views, iterate with for loops, and leverage std.mem utilities for searching and splitting.
Key concepts
- zig slices
- zig iteration
- zig for loop
- zig std.mem
- zig slice tutorial
Slices and Iteration
Slices are one of Zig's most important types. They provide a safe, fat-pointer view into arrays with both a pointer and a length, enabling flexible data access without copying.
Slices in Depth
A slice []T is a pointer and a length. It can reference any contiguous block of memory.
const std = @import("std");
pub fn main() !void {
const numbers = [_]i32{ 10, 20, 30, 40, 50, 60, 70, 80 };
// Slice the full array
const all = numbers[0..];
std.debug.print("All: ", .{});
for (all) |n| std.debug.print("{} ", .{n});
std.debug.print("(len={})\n", .{all.len});
// Partial slices
const first_three = numbers[0..3];
const middle = numbers[2..6];
const last_two = numbers[6..];
std.debug.print("First 3: ", .{});
for (first_three) |n| std.debug.print("{} ", .{n});
std.debug.print("\n", .{});
std.debug.print("Middle: ", .{});
for (middle) |n| std.debug.print("{} ", .{n});
std.debug.print("\n", .{});
std.debug.print("Last 2: ", .{});
for (last_two) |n| std.debug.print("{} ", .{n});
std.debug.print("\n", .{});
// Mutable slices
var mutable = [_]i32{ 1, 2, 3, 4, 5 };
const slice: []i32 = &mutable;
slice[0] = 100;
std.debug.print("Modified: {}\n", .{mutable[0]});
}
Sentinel-Terminated Slices
Some slices end with a special sentinel value, like null-terminated C strings.
const std = @import("std");
pub fn main() !void {
// String literals are null-terminated slices
const hello: [:0]const u8 = "Hello";
std.debug.print("String: {s} (len={})\n", .{ hello, hello.len });
// The sentinel is accessible at index len
std.debug.print("Sentinel at [{}]: {}\n", .{ hello.len, hello[hello.len] });
// Convert between slice types
const plain: []const u8 = hello; // [:0] -> [] is safe
std.debug.print("As plain slice: {s} (len={})\n", .{ plain, plain.len });
// Iterate over bytes
std.debug.print("Bytes: ", .{});
for (hello) |byte| {
std.debug.print("{} ", .{byte});
}
std.debug.print("\n", .{});
}
For Loop Patterns
Zig's for loops work with slices, arrays, and ranges.
const std = @import("std");
pub fn main() !void {
const names = [_][]const u8{ "Alice", "Bob", "Charlie", "Diana" };
// Basic iteration
std.debug.print("Names: ", .{});
for (names) |name| {
std.debug.print("{s} ", .{name});
}
std.debug.print("\n", .{});
// With index
for (names, 0..) |name, i| {
std.debug.print(" {}: {s}\n", .{ i, name });
}
// Multiple slices in parallel
const scores = [_]i32{ 95, 87, 92, 88 };
std.debug.print("\nScores:\n", .{});
for (names, scores) |name, score| {
std.debug.print(" {s}: {}\n", .{ name, score });
}
// Mutable iteration
var values = [_]i32{ 1, 2, 3, 4, 5 };
for (&values) |*v| {
v.* *= 2;
}
std.debug.print("\nDoubled: ", .{});
for (values) |v| std.debug.print("{} ", .{v});
std.debug.print("\n", .{});
}
std.mem Utilities
The standard library provides functions for working with memory and slices.
const std = @import("std");
pub fn main() !void {
const text = "Hello, World! Hello, Zig!";
// Find substring
if (std.mem.indexOf(u8, text, "World")) |pos| {
std.debug.print("'World' found at position {}\n", .{pos});
}
// Count occurrences
const count = std.mem.count(u8, text, "Hello");
std.debug.print("'Hello' appears {} times\n", .{count});
// Split
std.debug.print("\nSplit by space:\n", .{});
var iter = std.mem.splitScalar(u8, text, ' ');
while (iter.next()) |word| {
std.debug.print(" '{s}'\n", .{word});
}
// Equality
const a = "hello";
const b = "hello";
const c = "world";
std.debug.print("\n'hello' == 'hello': {}\n", .{std.mem.eql(u8, a, b)});
std.debug.print("'hello' == 'world': {}\n", .{std.mem.eql(u8, a, c)});
// Starts/ends with
std.debug.print("starts with 'Hello': {}\n", .{std.mem.startsWith(u8, text, "Hello")});
std.debug.print("ends with 'Zig!': {}\n", .{std.mem.endsWith(u8, text, "Zig!")});
}
Try It Yourself
const std = @import("std");
fn findMax(slice: []const i32) ?i32 {
if (slice.len == 0) return null;
var max = slice[0];
for (slice[1..]) |val| {
if (val > max) max = val;
}
return max;
}
fn findMin(slice: []const i32) ?i32 {
if (slice.len == 0) return null;
var min = slice[0];
for (slice[1..]) |val| {
if (val < min) min = val;
}
return min;
}
fn average(slice: []const i32) f64 {
if (slice.len == 0) return 0;
var sum: i64 = 0;
for (slice) |val| sum += val;
return @as(f64, @floatFromInt(sum)) / @as(f64, @floatFromInt(slice.len));
}
pub fn main() !void {
const data = [_]i32{ 42, 17, 93, 56, 28, 71, 5, 88 };
const slice: []const i32 = &data;
std.debug.print("Data: ", .{});
for (slice) |v| std.debug.print("{} ", .{v});
std.debug.print("\n", .{});
std.debug.print("Max: {}\n", .{findMax(slice).?});
std.debug.print("Min: {}\n", .{findMin(slice).?});
std.debug.print("Average: {d:.2}\n", .{average(slice)});
// Slicing to find max of first half vs second half
const mid = slice.len / 2;
const first_half = slice[0..mid];
const second_half = slice[mid..];
std.debug.print("\nFirst half max: {}\n", .{findMax(first_half).?});
std.debug.print("Second half max: {}\n", .{findMax(second_half).?});
}
Key Takeaways
- Slices (
[]T) are a pointer plus a length — safe and efficient - Sentinel-terminated slices (
[:0]const u8) support C interop forloops iterate over slices with optional index and parallel iteration- Use
&valueswith|*v|for mutable iteration std.memprovides splitting, searching, counting, and comparison utilities- Slicing is zero-cost — it creates a new view without copying data
Pro Tip: Prefer slices over arrays in function parameters. A function that takes
[]const i32works with arrays of any size, other slices, and dynamically allocated memory. This makes your functions more reusable without any runtime cost.
Next Steps
Now that you can work with slices and iterate over data, it's time to see how Zig models concurrency -- the std.Io interface, launching tasks, awaiting results, and cancelling work you no longer need.
Two-tier handoff: this document is the complete reading surface. Continue learning for stateful practice, progress, and real sandbox execution.