Zig String Operations
Zig's standard library provides a rich set of string operations through std.mem and std.fmt. Because strings in Zig are plain byte slices, these functions compose cleanly — you pass slices in and get slices or allocations back. This topic covers searching, splitting, trimming, replacing, and formatting strings.
Searching
const std = @import("std");
const text = "The quick brown fox jumps over the lazy dog";
// Find first occurrence of a substring
if (std.mem.indexOf(u8, text, "fox")) |pos| {
std.debug.print("'fox' at index {d}\n", .{pos}); // 16
}
// Find last occurrence
if (std.mem.lastIndexOf(u8, text, "the")) |pos| {
std.debug.print("last 'the' at {d}\n", .{pos}); // 31
}
// Check prefix and suffix
std.debug.print("starts with 'The': {}\n",
.{std.mem.startsWith(u8, text, "The")}); // true
std.debug.print("ends with 'dog': {}\n",
.{std.mem.endsWith(u8, text, "dog")}); // true
// Count occurrences
const n = std.mem.count(u8, text, "the");
std.debug.print("'the' count: {d}\n", .{n}); // 1 (case-sensitive)
Slicing Substrings
const sentence = "Hello, Zig World!";
// Extract a fixed range
const sub = sentence[7..10]; // "Zig"
std.debug.print("{s}\n", .{sub});
// Extract from a position to end
const tail = sentence[7..]; // "Zig World!"
std.debug.print("{s}\n", .{tail});
"Hello, Zig World!" 0123456789... [7..10] → index 7='Z', 8='i', 9='g' → "Zig" [7..] → index 7 to end → "Zig World!"
Splitting Strings
Use an iterator to split by a delimiter without allocating:
const csv = "apple,banana,cherry,date";
var it = std.mem.splitScalar(u8, csv, ',');
while (it.next()) |token| {
std.debug.print("Fruit: {s}\n", .{token});
}
"apple,banana,cherry,date"
| | |
split on ','
→ "apple" → "banana" → "cherry" → "date"
For splitting on a multi-character delimiter, use std.mem.splitSequence:
var it2 = std.mem.splitSequence(u8, "one::two::three", "::");
while (it2.next()) |part| {
std.debug.print("{s}\n", .{part});
}
// one
// two
// three
Trimming Whitespace
const padded = " hello world ";
const trimmed = std.mem.trim(u8, padded, &std.ascii.whitespace);
std.debug.print("'{s}'\n", .{trimmed}); // 'hello world'
// Trim only left side:
const ltrimmed = std.mem.trimLeft(u8, padded, &std.ascii.whitespace);
// Trim only right side:
const rtrimmed = std.mem.trimRight(u8, padded, &std.ascii.whitespace);
Before: " hello world "
↑↑↑ ↑↑↑
whitespace whitespace
After trim: "hello world"
Replacing Characters
var buf: [64]u8 = undefined;
const src = "hello-zig-world";
// Replace all '-' with '_'
_ = std.mem.replace(u8, src, "-", "_", &buf);
const result = buf[0 .. src.len];
std.debug.print("{s}\n", .{result}); // hello_zig_world
Converting Case
var lower_buf: [20]u8 = undefined;
var upper_buf: [20]u8 = undefined;
const word = "ZigLang";
for (word, 0..) |c, i| lower_buf[i] = std.ascii.toLower(c);
for (word, 0..) |c, i| upper_buf[i] = std.ascii.toUpper(c);
std.debug.print("Lower: {s}\n", .{lower_buf[0..word.len]}); // ziglang
std.debug.print("Upper: {s}\n", .{upper_buf[0..word.len]}); // ZIGLANG
Checking Character Types
const std_ascii = std.ascii;
std.debug.print("{}\n", .{std_ascii.isAlpha('A')}); // true
std.debug.print("{}\n", .{std_ascii.isDigit('7')}); // true
std.debug.print("{}\n", .{std_ascii.isAlNum('z')}); // true
std.debug.print("{}\n", .{std_ascii.isSpace(' ')}); // true
std.debug.print("{}\n", .{std_ascii.isUpper('A')}); // true
std.debug.print("{}\n", .{std_ascii.isLower('a')}); // true
std.debug.print("{}\n", .{std_ascii.isPunct('!')}); // true
Formatting Numbers into Strings
var buf: [32]u8 = undefined;
// Integer to string
const int_str = try std.fmt.bufPrint(&buf, "{d}", .{12345});
std.debug.print("{s}\n", .{int_str}); // 12345
// Float with 2 decimal places
const flt_str = try std.fmt.bufPrint(&buf, "{d:.2}", .{3.14159});
std.debug.print("{s}\n", .{flt_str}); // 3.14
// Hex
const hex_str = try std.fmt.bufPrint(&buf, "{x}", .{255});
std.debug.print("{s}\n", .{hex_str}); // ff
Parsing Strings into Numbers
const n = try std.fmt.parseInt(i32, "-42", 10); // -42 const u = try std.fmt.parseInt(u32, "100", 10); // 100 const h = try std.fmt.parseInt(u8, "FF", 16); // 255 (hex) const f = try std.fmt.parseFloat(f64, "3.14"); // 3.14
Joining Strings
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
const parts = [_][]const u8{ "Zig", "is", "awesome" };
const joined = try std.mem.join(alloc, " ", &parts);
defer alloc.free(joined);
std.debug.print("{s}\n", .{joined}); // Zig is awesome
}
Practical Example: CSV Parser
const std = @import("std");
pub fn main() void {
const data =
\\Name,Score,Grade
\\Alice,95,A
\\Bob,82,B
\\Carol,91,A
;
var lines = std.mem.splitScalar(u8, data, '\n');
var first = true;
while (lines.next()) |line| {
if (first) { first = false; continue; } // skip header
var fields = std.mem.splitScalar(u8, line, ',');
const name = fields.next() orelse continue;
const score = fields.next() orelse continue;
const grade = fields.next() orelse continue;
std.debug.print("{s} scored {s} ({s})\n", .{name, score, grade});
}
}
Output:
Alice scored 95 (A) Bob scored 82 (B) Carol scored 91 (A)
