str
import str from "str";
let parts: [3][]const u8 = ["usr", "local", "bin"];
let p: string = str.join(parts[0..3], "/"); // "usr/local/bin"
let title: string = str.to_upper("tape"); // "TAPE"
let row: string = str.pad_right("name", 8, 32); // "name "str makes new strings. Everything that only looks at text (searching,
comparing, trimming, splitting) is in bytes and works on
views without copying; str is for when you need an owned string as the
result.
Every text parameter is a []const u8, so you can pass a string, a
literal or a slice of either (see
Strings as byte views). Every string a function
returns is new and yours: it’s freed automatically when it goes out of
scope.
str is a pure module: it allocates, but has no other effect.
Builder
A Builder collects text piece by piece and turns it into a string at the
end. Appending is amortized constant time, so it’s the way to build long
text in a loop.
| Function | Does |
|---|---|
str.builder() -> Builder | An empty builder (allocates on the first append) |
str.with_capacity(cap: i64) -> Builder | An empty builder with room for cap bytes |
b.append(s: []const u8) | Appends the bytes of s |
b.append_byte(b: u8) | Appends one byte |
b.append_char(cp: u32) | Appends a code point as UTF-8 (U+FFFD if it isn’t a valid one) |
b.append_i64(v: i64) | Appends v in decimal, as "{v}" writes it |
b.len() -> i64 | Bytes appended so far |
b.as_view() -> []const u8 | The bytes so far, as a view |
b.clear() | Empties the builder, keeping its memory |
b.truncate(n: i64) | Keeps only the first n bytes (traps if n is negative or past the end) |
b.finish() -> string | The contents as a new string; the builder is emptied |
b.free() | Releases the builder’s memory without making a string |
fn csv_line(values: []const i64) -> string {
var b: str.Builder = str.builder();
for (i in 0..values.len) {
if (i > 0) { b.append(","); }
b.append_i64(values[i]);
}
return b.finish();
}A builder holds memory until finish() or free(). If a function can
return before finishing, free it with defer; freeing a builder that was
already finished does nothing:
fn render(items: []const []const u8) -> string or RenderError {
var b: str.Builder = str.builder();
defer b.free();
for (item in items) {
if (item.len == 0) { return RenderError.EmptyItem; } // b is freed
b.append(item);
b.append_byte(10);
}
return b.finish();
}The view from as_view() points into the builder’s memory: use it before
the next append, clear, finish or free.
Building strings
| Function | Returns |
|---|---|
from_view(s) -> string | A copy of s as an owned string |
repeat(s, n: i64) -> string | s repeated n times |
join(parts: []const []const u8, sep) -> string | The parts with sep between them |
replace(s, needle, replacement) -> string | s with every needle replaced |
to_upper(s) -> string | s with ASCII a-z made upper case |
to_lower(s) -> string | s with ASCII A-Z made lower case |
let line: string = str.repeat("-", 20); // 20 dashes
let words: [3][]const u8 = ["one", "two", "three"];
let csv: string = str.join(words[0..3], ","); // "one,two,three"
let path: string = str.replace("a\\b\\c", "\\", "/"); // "a/b/c"
let lower: string = str.to_lower("Content-Type"); // "content-type"replace replaces non-overlapping occurrences from left to right:
str.replace("aaa", "aa", "b") is "ba". to_upper and to_lower change
only ASCII letters; other bytes, including UTF-8 text, are copied unchanged.
repeat stops the program with a trap
when n is negative, and replace when needle is empty.
Padding
| Function | Returns |
|---|---|
pad_left(s, width: i64, fill: u8) -> string | s right-aligned: fill added before it |
pad_right(s, width: i64, fill: u8) -> string | s left-aligned: fill added after it |
center(s, width: i64, fill: u8) -> string | s centered, the extra fill byte after it |
The width counts characters, not bytes: "ä" is one character. A string
that is already width characters or wider comes back unchanged (never
cut). fill must be an ASCII byte (below 128); anything else traps.
let id: string = str.pad_left("42", 5, 48); // "00042"
let cell: string = str.pad_right("tape", 8, 32); // "tape "
let title: string = str.center("menu", 10, 61); // "===menu==="
let wide: string = str.pad_left("toolong", 3, 32); // "toolong"Searching and splitting
For convenience, str repeats the most used view functions from
bytes. They are the same functions: no copies, results are
views into the input.
| Function | Returns |
|---|---|
eq(a, b) -> bool | true when a and b have the same bytes |
contains(haystack, needle) -> bool | true when needle occurs |
starts_with(s, prefix) -> bool | true when s begins with prefix |
ends_with(s, suffix) -> bool | true when s ends with suffix |
index_of(haystack, needle) -> ?i64 | Offset of the first needle, or null |
trim(s) -> []const u8 | s without leading and trailing whitespace |
split(s, sep) -> bytes.SplitIter | The parts between separators, for for |
lines(s) -> bytes.LineIter | The lines of s, for for |
for (field in str.split("a, b, c", ",")) {
let f: []const u8 = str.trim(field); // "a", "b", "c"
}Anything else (last_index_of, cut, trim_left, count, …) is in
bytes.
C strings and the old API
str before 2.1 worked on NUL-terminated *u8 pointers with separate
u32 lengths. Those functions moved to cstr unchanged and are
deprecated there; cstr lists the replacement for each.
Last modified: