std.str

The complete UTF-8 string toolkit. Every operation is character-indexed, so it is correct for multi-byte text.

import std.str

str.title("the quick fox")  # "The Quick Fox"
str.pad_start("7", 4, "0")  # "0007"
str.substring("hello world", -5)  # "world"
str.replace_first("a-b-c", "-", "/")  # "a/b-c"
str.count("banana", "na")  # 2

The most common operations are global built-ins already - upper, lower, trim, split, join, replace, contains, starts_with, ends_with, chars, lines, reverse, index_of, len. Import this module for the fuller set.

import std.str binds string, shadowing the global string() constructor for the rest of the scope. Use string.from(x) to convert, or import std.str as s to keep string() free.

Case

upper, lower, capitalize (first letter up, rest down), title (each word), swapcase, and eq_ignore_case(a, b).

eq_ignore_case compares Unicode lowercased forms. It does not perform full case folding, so German ß does not compare equal to ss.

Trim

str.trim(s)                  # whitespace
str.trim(s, "x")             # any character in the set
str.trim("xxhixx", "x")      # "hi"
str.trim_prefix(s, "www.")   # a fixed affix, once
str.trim_suffix(s, ".com")

trim, trim_start, trim_end take an optional character set, not a substring - trim(s, "ab") strips any a or b from the ends.

Pad

str.pad_start(s, 8)          # fill defaults to a space
str.pad_end(s, 8, ".")
str.center(s, 20, "-")       # odd leftover goes right
str.zfill("-5", 4)           # "-005"

zfill pads with zeros after any leading sign, which is the one case pad_start(s, w, "0") gets wrong. A multi-character fill cycles and truncates to the gap.

Extract

str.substring(s, 1, 4)       # clamps
str.substring(s, -5)         # negative from the end
str.char_at(s, 0)            # errors out of range

substring clamps; char_at errors. Same asymmetry as slices versus indexing.

Code points

str.ord("A")     # 65
str.ord("𝄞")     # 119070 - a non-BMP character
str.chr(65)      # "A"

Scalar values, not bytes, so the pair round-trips for any character. ord("") errors; chr errors on a negative value, above U+10FFFF, or in the surrogate range.

contains, starts_with, ends_with, index_of and last_index_of (character offset, or -1), count(s, sub) (non-overlapping).

Split and join

str.split("a,b,c", ",", 1)       # ["a", "b,c"]   limit caps the splits
str.rsplit("a,b,c", ",", 1)      # ["a,b", "c"]   counted from the right
str.partition("k=v=w", "=")      # ["k", "=", "v=w"]
str.rpartition("k=v=w", "=")     # ["k=v", "=", "w"]
str.split_whitespace(s)
str.lines(s)
str.join(xs, ", ")

partition and rpartition split once and always return a 3-list, so a destructure never fails:

let (key, sep, value) = str.partition(line, "=")

With no match, partition keeps the string in the head and rpartition in the tail.

Character-class tests

True only if the string is non-empty and every character is in the class: is_digit (Unicode numeric), is_alpha, is_alnum, is_space, is_ascii.

is_upper and is_lower need at least one cased character and no cased character of the opposite case - so "A1!" is upper, and "123" is neither.

The empty string is false for every predicate, one uniform rule. For "empty or whitespace" use string.is_blank.

Info and convert

len, is_empty, is_blank, repeat(s, n), from(x).

From bytes

str.from_utf8(b)         # strict decode, catchable
str.from_utf8_lossy(b)   # opt-in replacement characters

See Bytes.