Skip to main content

9  String and Builder

Edva separates text and byte buffers into two paired types: - String: Immutable, NUL-reserved 16-byte fat pointer { i64 len, ptr data } with arena generation-tag safety. - Builder: Mutable, growable 24-byte arena buffer { i64 len, ptr data, i64 cap } that freezes into a String in \(O(1)\) via unary +b.

9.1 Architecture and ABI comparison

Property String Builder
Runtime layout { i64 len, ptr data } (16 bytes) { i64 len, ptr data, i64 cap } (24 bytes)
Mutability Strictly immutable (s(0) = x rejected) Mutable in place (+=, = (...), ?b = n, @[&b] = x)
NUL-reserved tail Guaranteed data[len] == '\0' on whole strings Guaranteed upon +b freeze
Generation tag Upper 16 bits of len (len >> 48); traps with E4010 on stale read Allocated in active arena
Aliasing semantics Value descriptor sharing immutable bytes Shared descriptor until +b detaches buffer and resets descriptor to {0, null, 0}

9.2 String literals, margin stripping, and interpolation

Feature Syntax Specification Diagnostic
Single-line literal "Hello\n\t\"world\"" Supports \n, \t, \r, \0, \a, \b, \f, \v, \e, \", \', \\, \$, \xHH, \u{HEX} E1003 / E1006
Multi-line literal " followed immediately by \n Strips opening \n and strips the first content line’s leading indentation (baseline margin) from all lines E1017: line indented less than baseline margin
Line continuation Trailing \ before \n Suppresses newline while maintaining baseline margin stripping on the next line —
Closing quote placement Own line vs end of content line Closing " on its own line preserves trailing \n; closing " at end of text omits trailing \n —
Interpolation (${...}) "val = ${x + 1}" Lowers to %to_str(expr) for String, Rune, integers, and floats; in b += "...", streams directly into b without intermediate allocations E3105: non-scalar in ${...}
#use io

user = "Alice"
items = 3
doc = "
   User: ${user}
   Count: ${items}
   "
io::out $ doc

9.3 Arena generation tags and stale-read protection (E4010)

Mechanism Specification
Tag encoding Upper 16 bits of the 64-bit len word (len >> 48) store the arena scope generation; actual byte length is len & 0x0000FFFFFFFFFFFF
Tag 0 (Immortal) String literals and top-level allocations outside restore scopes carry tag 0 and never expire
Active scope validation Scoped arena restores track active generation IDs (1..65535, wrapping to 1 and skipping active IDs). Reading a String whose tag is no longer active traps with E4010
FFI pointer masking &s returns the raw data pointer directly (NUL-terminated); %ptrmask is available for tagged raw addresses

9.4 String and Builder operations

Operation Syntax Operand Result Type Semantics
Byte length ?s / ?b String / Builder Int Number of UTF-8 bytes currently stored (.len is rejected with E3065)
Rune count ??s String Int Number of UTF-8 Unicode codepoints
Data pointer &s / &b String / Builder Addr Raw pointer to byte buffer (&s is NUL-terminated for C FFI)
Byte index s(i) String, i: Int Int Unsigned byte value (0..255) at i (aborts on out-of-bounds)
Substring slice s(a..b) / s(a...b) String, range String Zero-copy sub-range view (a.., ..b, -k.. supported)
Concatenation s1 + s2 / s + byte_or_rune String String Allocates concatenated String in arena
Segment equality s == ("pre", mid, "suf") String, tuple of String <> Zero-allocation length + segment memory comparison
Allocate Builder b = {cap} cap: Int Builder Allocates mutable arena buffer with initial capacity cap
Single append b += x x: String, Rune, or byte Int () Appends string bytes, 1–4 UTF-8 rune bytes, or 1 byte (0..255) in place
Peephole chain append b += s1 + s2 + s3 String chain () Flattens into sequential in-place appends without intermediate String allocation
Tuple mass-append b += (a, b, c) or b = (a, b, c) Tuple of String/Rune/Int () Pre-computes total byte length, reserves capacity once, and writes all items contiguously
Set Builder length ?b = new_len Builder, new_len: Int () Sets written byte length (0 <= new_len <= cap; aborts with E4008 if out of bounds)
Copy-and-append b + x Builder, x Builder Clones b and appends x, leaving b unchanged
Deep copy ++s / ++b String / Builder Same Allocates an independent arena copy of the buffer
Freeze Builder +b Builder String Writes trailing \0, transfers buffer to String in \(O(1)\), and resets b (and any aliases) to {0, null, 0}
#use io

b = {64}
b += ("SELECT ", "id, name", " FROM users WHERE active = ", '1')
query = +b

io::out $ query
io::out $ (?query, ?b, query(0..6))