Strings
const std = @import("std");
const expect = std.testing.expect;
const expectEqualStrings = std.testing.expectEqualStrings;
test "compare content, not pointers" {
// There is no == for slices; that would compare pointer and length.
try expect(std.mem.eql(u8, "zig", "zig"));
try expect(std.mem.startsWith(u8, "build.zig", "build"));
try expect(std.mem.endsWith(u8, "build.zig", ".zig"));
}
test "search" {
// indexOf returns the byte offset of the first match, or null.
try expect(std.mem.indexOf(u8, "hello world", "world").? == 6);
try expect(std.mem.indexOf(u8, "hello world", "moon") == null);
try expect(std.mem.lastIndexOfScalar(u8, "a.b.c", '.').? == 3);
try expect(std.mem.count(u8, "banana", "an") == 2);
}
test "trim strips a set of bytes, not a pattern" {
// The second slice is the set of bytes to remove from the ends.
try expectEqualStrings("data", std.mem.trim(u8, " data\n", " \n"));
try expectEqualStrings("data ", std.mem.trimStart(u8, " data ", " "));
try expectEqualStrings(" data", std.mem.trimEnd(u8, " data ", " "));
}
test "split keeps empty fields, tokenize skips them" {
// split: one entry per delimiter. "a,,b" has three fields, one empty.
// Right for CSV-shaped input where an empty field means something.
var fields = std.mem.splitScalar(u8, "a,,b", ',');
try expectEqualStrings("a", fields.next().?);
try expectEqualStrings("", fields.next().?);
try expectEqualStrings("b", fields.next().?);
try expect(fields.next() == null);
// tokenize: runs of delimiters collapse. Right for whitespace.
var words = std.mem.tokenizeScalar(u8, " one two ", ' ');
try expectEqualStrings("one", words.next().?);
try expectEqualStrings("two", words.next().?);
try expect(words.next() == null);
}
test "join and replace return new memory" {
const gpa = std.testing.allocator;
const path = try std.mem.join(gpa, "/", &.{ "usr", "local", "bin" });
defer gpa.free(path);
try expectEqualStrings("usr/local/bin", path);
const snake = try std.mem.replaceOwned(u8, gpa, "a-b-c", "-", "_");
defer gpa.free(snake);
try expectEqualStrings("a_b_c", snake);
}
test "case functions are ASCII-only on purpose" {
// Real case mapping needs Unicode tables the std library does not ship.
try expect(std.ascii.eqlIgnoreCase("Zig", "zIG"));
const gpa = std.testing.allocator;
const lower = try std.ascii.allocLowerString(gpa, "MiXeD");
defer gpa.free(lower);
try expectEqualStrings("mixed", lower);
}There is no string type
A string is []const u8: a slice of bytes. That means == compares the
pointer and length, not the content, and the compiler will tell you so.
Content comparison is std.mem.eql(u8, a, b). Everything else you would
expect from a string class is a free function in std.mem that works on any
slice type, which is why each call names the element type first.
split or tokenize
The two look interchangeable until the input has consecutive delimiters:
Input "a,,b" with , | Yields |
|---|---|
splitScalar | "a", "", "b" |
tokenizeScalar | "a", "b" |
split preserves empty fields, which is what CSV-shaped data needs.
tokenize collapses runs of delimiters, which is what whitespace needs. Both
have Any and Sequence variants: splitAny treats the delimiter string as
a set of single bytes, splitSequence matches it whole.
What allocates and what does not
trim, split, and tokenize return views into the original bytes: no
copy, but the result is only valid as long as the original. join,
replaceOwned, and std.ascii.allocLowerString build new strings, so they
take an allocator and you own the result.
ASCII on purpose
std.ascii stops at byte 127. Case mapping beyond that needs Unicode tables
the standard library does not ship; see the Unicode chapter for what it does
give you.