Strings & Characters

GX strings are powerful out of the box — interpolation, built-in methods, and O(1) length access. No strlen loops, no format specifiers.

The str Type

Strings in GX are fat strings — they store both a pointer to the text and the length:

fn main() {
    var greeting = "Hello, GX!"

    print("Text: {greeting}\n")
    print("Length: {greeting.len}\n")
}

Getting the length is instant — no scanning for null terminators.

String Interpolation

Put any expression inside {} in a string:

fn main() {
    var name = "Alice"
    var age = 28
    var score = 95.5

    print("Name: {name}\n")
    print("Age: {age}\n")
    print("Score: {score}\n")
    print("{name} is {age} years old with a score of {score}\n")
}

Interpolation works with variables, expressions, and function calls — anything that produces a value.

Escape Sequences

Special characters use backslash escapes:

EscapeMeaning
\nNewline
\tTab
\\Backslash
\"Double quote
\0Null character
fn main() {
    print("Line 1\nLine 2\n")
    print("Column A\tColumn B\n")
    print("She said \"hello\"\n")
    print("Path: C:\\Users\\GX\n")
}

String Concatenation

Use + to join strings:

fn main() {
    var first = "Hello"
    var second = " World"
    var combined = first + second

    print("{combined}\n")
}

The char Type

Single characters use single quotes:

fn main() {
    var letter:char = 'A'
    var digit:char = '7'

    print("letter = {letter}\n")
    print("digit = {digit}\n")
}

Built-in String Methods

GX strings come with methods for common operations — no imports needed:

Searching

fn main() {
    var text = "Hello, World!"

    print("find 'World': {text.find(\"World\")}\n")       // 7
    print("contains 'Hello': {text.contains(\"Hello\")}\n") // true
    print("starts_with 'He': {text.starts_with(\"He\")}\n") // true
    print("ends_with '!': {text.ends_with(\"!\")}\n")       // true
    print("count 'l': {text.count(\"l\")}\n")               // 3
}

Extracting and Trimming

fn main() {
    var text = "  Hello, World!  "

    print("trimmed: '{text.trim()}'\n")
    print("sub(2,7): '{text.sub(2, 7)}'\n")
}

Transforming

fn main() {
    var text = "Hello, World!"

    print("upper: {text.upper()}\n")
    print("lower: {text.lower()}\n")
    print("replace: {text.replace(\"World\", \"GX\")}\n")
    print("repeat: {text.repeat(3)}\n")
}

Splitting

fn main() {
    var csv = "apple,banana,cherry"
    var parts = csv.split(",")

    print("Parts ({parts.len}):\n")
    for (var part in parts) {
        print("  - {part}\n")
    }
}

Try it — Play with string methods in the Playground.


Expert Corner

Why fat strings? Traditional C strings are null-terminated — finding the length requires walking every character (strlen is O(n)). GX’s str stores { const char* ptr; int64_t len }, so .len is O(1). This also enables safe slicing without copying: sub(2, 7) returns a view into the original string.

Zero-copy vs scratch-buffer methods: Methods like find, contains, sub, trim, starts_with, and ends_with never allocate memory — they work directly on the existing string data. Methods like upper, lower, replace, and repeat use an internal scratch buffer since they need to produce modified text.

C interop: Use .cstr to get a null-terminated const char* for passing to C functions. Use .ptr when you have a C function that accepts a pointer and length separately. GX automatically converts between str and cstr at function call boundaries.

String interpolation is resolved at parse time. The parser only triggers interpolation when { is followed by an identifier character (a-z, A-Z, _), digit, (, -, or !. This means GLSL shader code like void main() { } inside strings works untouched — the space after { prevents interpolation.