Threadon / Strings

Strings

text: indexing, slicing, and interpolation

Literals and escapes

A String is a piece of text, written with double quotes. Single quotes are not strings in Threadon. The basic rules live on the Basics page; this page covers what you can do with a string.

Five escapes work inside a string:

EscapeMeaning
\nnewline
\ttab
\"a literal double quote
\\a literal backslash
\{a literal {; only needed inside f"..."/t"..." (and \} for a literal })
def main() -> Int32
    print("say \"hi\"")
    print("back\\slash")
    return 0
say "hi" back\slash

Joining and converting

Join strings with +. Both sides must be String — "answer: " + 42 is a compile error. Convert with the cast form String(...) first, or use an interpolated string (below):

def main() -> Int32
    name: String = "John"
    print("Hello " + name)
    print("the number is " + String(42))
    return 0
Hello John the number is 42

Indexing

s[i] reads the character at position i, counting from 0, and returns it as a one-character String. The index must be an integer.

def main() -> Int32
    s: String = "hello"
    print(s[0])
    print(s[1])
    i: Int32 = 4
    print(s[i])
    return 0
h e o

Indexing the character you get back as a normal one-character string: it compares equal to the matching literal, can be printed, and can be concatenated.

def main() -> Int32
    s: String = "hi"
    print(s[0] == "h")
    return 0
True

Bounds are checked only under --debug. An index past the end — or a negative index — is a runtime error in debug mode (String index out of bounds). Without --debug it's undefined behavior, just like the equivalent list read. See Debug.

Characters are read-only. s[0] = "J" is a compile error (Cannot assign to a character of a String). There is no way to edit a string in place; build a new one instead.

Slicing

s[start:end] copies the characters from start up to (but not including) end. Both bounds are optional and must be integers.

SliceResult on "hello"Meaning
s[:]hellowhole string
s[:2]hefirst two characters
s[2:]lloeverything from index 2 on
s[1:4]ellthree characters from position 1
s[0:0]empty slice
def main() -> Int32
    s: String = "hello"
    print(s[1:4])
    print(s[:2])
    print(s[2:])
    print(s[:])
    return 0
ell he llo hello

Bounds are clamped to the string. A negative bound counts from the end, then the result is clamped to [0, len]. A slice where the start is past the end — or the range is empty — gives an empty string. No bounds error here, ever; slicing is always safe.

def main() -> Int32
    s: String = "hello"
    print(s[-3:-1])    # last three up to last one
    print(s[-100:100])  # clamped to the whole string
    print(s[3:1])       # start past end: empty
    return 0
ll hello

There is no step argument. s[::2] is rejected (Slice step is not supported).

Comparing strings by content

All six comparison operators work on strings and compare contents, not pointers: ==, !=, and the ordering operators <, <=, >, >= compare lexicographically (byte by byte).

def main() -> Int32
    s: String = "hello"
    print(s[1:4] == "ell")
    print(s < "jello")
    print("abc" < "abd")
    print("hi" != "hi")
    return 0
True True True False

Both sides must still be String: "1" < "2" is a True textual comparison, while "1" < 2 is a compile error.

Interpolated strings: f"..." and t"..."

An interpolated string embeds expressions between braces. The expression is normal Threadon — variables, arithmetic, even other calls — and the result is converted to text. There are two kinds, and the difference matters.

f"..." — eager

The expression is evaluated once, at the point the string is built. Later changes to the variables do not show up.

def main() -> Int32
    x: Int32 = 3
    a: String = f"x is {x} and half is {x / 2}"
    x = 99
    print(a)
    return 0
x is 3 and half is 1

t"..." — lazy template

Captures the variables and renders them only when the string is read (printed, concatenated, compared, indexed…). The captured variable's value at read time is what appears.

def main() -> Int32
    x: Int32 = 3
    a: String = t"x is {x}"
    x = 99
    print(a)
    return 0
x is 99

Two escapes exist to keep braces literal inside an interpolated string: \{ and \}.

def main() -> Int32
    print(f"a set: \{1, 2\}")
    return 0
a set: {1, 2}

Use f"..." when you want a plain string at a point in time; use t"..." when you want a live view of variables (for example, a template read out repeatedly). The example 15_string_ops shows plain string features.

What's missing

The full list of missing things is on the Limits page.