ktm v0.28.3

Strings

ktm has three ways to hold text. A String is text you own: it grows and you free it. A u8[] is a view of text someone else owns: what functions take to read text. A u8 * is a C string, for C functions. This chapter covers all three and how they convert.

TypeWhat it isUse it for
StringA pointer, a length and a capacity (24 bytes). Owned, growable, always ends in a NULText you build or keep
u8[]A pointer and a length (16 bytes). A viewParameters, parts of text, literals
u8 *A pointer to bytes ending in a NULC functions

Making a String

A String you get from the str module is yours, to free when you are done. scoped frees it at the end of the block.

import str;

void main() {
    scoped String s = str.from("Hello");
    s.append(", world");
    s.appendFormat(" %d", 2026);
    printf("%s (%d bytes)\n", s, s.len);
}
Hello, world 2026 (17 bytes)
import str;

void main() {
    scoped String a = str.new();               // empty, nothing allocated yet
    scoped String b = str.from("copied");      // a copy of any text
    scoped String c = str.withCap(64);         // empty, with room for 64 bytes
    scoped String d = str.format("%d-%s", 42, "x");
    String e;                              // a declared String starts empty
    a.append("grown");
    printf("[%s] [%s] [%s] [%s] [%d]\n", a, b, c, d, e.len);
}
[grown] [copied] [] [42-x] [0]

str.format takes printf's format and returns the result as a new String:

import str;

void main() {
    scoped String line = str.format("%-6s|%5.1f|%03d", "id", 3.14159, 7);
    printf("[%s]\n", line);
}
[id    |  3.1|007]

Changing a String

import str;

void main() {
    scoped String s = str.from("found 3 files");
    s.insert(0, "> ");
    s.replace("files", "logs");
    s.append(" in /tmp");
    s.appendByte('!');
    printf("%s\n", s);
    s.remove(0, 2);
    s.toUpper();
    printf("%s\n", s);
    s.reverse();
    printf("%s\n", s);
    s.clear();
    printf("[%s] %d\n", s, s.len);
}
> found 3 logs in /tmp!
FOUND 3 LOGS IN /TMP!
!PMT/ NI SGOL 3 DNUOF
[] 0

Building text in a loop:

import str;

void main() {
    scoped String csv = str.new();
    i64 values[] = {3, 1, 4, 1, 5};
    for (i64 i = 0; i < 5; i++) {
        if (i > 0) { csv.appendByte(','); }
        csv.appendFormat("%d", values[i]);
    }
    printf("%s\n", csv);
}
3,1,4,1,5

Reading text

A String converts to a u8[], and so does a literal. So every function that reads text takes either.

import str;

void main() {
    scoped String s = str.from("Hello, world");
    u8[] hello = s[0:5];
    printf("%s %c %d\n", hello, s[7], s.len);
    printf("%d %d %d\n", s.startsWith("Hell"), s.find("world"), s.contains("xyz"));
    printf("%d %d\n", s.equals("Hello, world"), s.endsWith("!"));
}
Hello w 12
1 7 0
1 0

The functions that read text are in str, and can be called as methods on a u8[] or a String: s.find("x") is str.find(s, "x").

import str;

void main() {
    u8[] line = "  name = ktm  ";
    u8[] t = str.trim(line);
    i64 eq = t.find("=");
    u8[] key = str.trim(t[0:eq]);
    u8[] value = str.trim(t[eq + 1:]);
    printf("[%s] [%s]\n", key, value);
}
[name] [ktm]

Splitting and joining

import str;

void main() {
    scoped u8[][] parts = str.split("red,green,,blue", ",");
    printf("%d parts:", parts.len);
    for (auto p : parts) {
        printf(" [%s]", p);
    }
    printf("\n");
    scoped String joined = str.join(parts, " + ");
    printf("%s\n", joined);
}
4 parts: [red] [green] [] [blue]
red + green +  + blue

The parts are views into the text that was split: the list is yours to free, the parts are not.

Numbers in text

import str;

void main() {
    i64 n;
    f64 f;
    if (str.toInt("42", &n)) {
        printf("%d\n", n + 1);
    }
    if (!str.toInt("42x", &n)) {
        printf("42x isn't an integer\n");
    }
    if (str.toFloat("2.5", &f)) {
        printf("%.2f\n", f * 2);
    }
    scoped String back = str.format("%d", 1234);
    printf("%s has %d digits\n", back, back.len);
}
43
42x isn't an integer
5.00
1234 has 4 digits

Copies share bytes

Assigning a String copies its three fields, so both variables point at the same bytes, and only one of them may free them. clone() makes a copy with bytes of its own.

import str;

void main() {
    scoped String a = str.from("shared");
    String alias = a;              // the same bytes: don't free both
    scoped String own = a.clone();     // bytes of its own
    own.toUpper();
    printf("%s %s %s\n", a, alias, own);
}
shared shared SHARED

Bytes, not characters

len and s[i] count bytes. Text is UTF-8 by convention, so a character outside ASCII takes more than one byte.

import str;

void main() {
    u8[] word = "naïve";
    printf("%d bytes, %d characters\n", word.len, str.runeCount(word));
}
6 bytes, 5 characters

Literals are read-only

A string literal's bytes can't be changed. A write the compiler can see is an error that says what to do; one it can't see crashes at run time. Copy the text into a String first.

does not compilevoid main() {
    u8[] a = "abc";
    a[0] = 'x';
}
read-only
import str;

void main() {
    scoped String a = str.from("abc");
    a[0] = 'x';
    printf("%s\n", a);
}
xbc

A u8 array initialised from a literal gets its own writable copy:

void main() {
    u8 buf[] = "abc";        // 4 bytes: a, b, c and the NUL
    u8 name[8] = "hi";       // the 6 bytes after "hi" are zero
    buf[0] = 'X';
    printf("%s %d %s %d\n", buf, sizeof(buf), name, sizeof(name));
}
Xbc 4 hi 8

C strings

A C string is a u8 * to bytes that end in a NUL: what C functions take and return. A String's bytes always end in a NUL, so a String variable goes where a u8 * is expected as it is, with no copy. cstr.view(p) turns a C string into a u8[].

import cstr, libc, str;

void main() {
    scoped String s = str.from("for C");
    u8 *c = s;
    u8[] back = cstr.view(c);
    printf("%s %d %d\n", c, back.len, libc.strlen(s));
}
for C 5 5

printf and %s

%s takes a C string, a u8[] or a String. A width works (%-10s); a precision cuts the text.

import str;

void main() {
    scoped String s = str.from("ktm");
    u8[] part = "language"[0:4];
    printf("[%-6s][%6s][%s][%.2s]\n", s, part, "c string", "abcdef");
}
[ktm   ][  lang][c string][ab]

Which one to use