This library is in flux. APIs may change without notice. Generated from source with koruc 0.1.7 on 8/19/2026.
String
~import std/stringstd/string - String library with view/instance ownership model
string.kz · 17 tors
std/string - String library with view/instance ownership model
Ownership states:
<view!> - read-only access, can be promoted to instance
<instance!> - full control, can read/write/free
The String struct carries its allocator, so it can be freed anywhere.
Phantom lifecycles
Derived from the phantom labels in the declarations below — state! issues an
obligation the compiler will chase, !state discharges it, a bare state holds it without moving it. Nothing here is hand-drawn.
String 2 states view!instance!// Creation events - different allocator sources
//
// Create with explicit allocator
~pub tor new { allocator: std.mem.Allocator, text: string }
| ok *String<view!>
| err string// Create with page allocator (simple, for demos)
~pub tor from-page { text: string }
| ok *String<view!>
| err string// Ownership transitions
//
// Take ownership - view becomes instance
~pub tor take { s: *String<!view> } -> *String<instance!>// Release ownership - instance becomes view
~pub tor release { s: *String<!instance> } -> *String<view!>// Cleanup
//
// Free - works on either view or instance (consume-marker on BOTH members, so
// the union discharges either state; `<!view|instance>` marked only `view`,
// hiding `free` from `instance!` obligations — the discharge-finder's
// .state_union branch keys off each member's own consume marker).
~pub tor free { s: *String<!view|!instance> }// Read operations - work on view or instance
~pub tor read { s: *String<view|instance> } -> string~pub tor len { s: *String<view|instance> } -> usize// Query / transform operations (FRONTIERS gap 4)
//
// Safe-by-construction: each op returns a scalar or a NEW owned String, never
// a borrowed sub-view of `s`. So the ratified borrow model holds trivially —
// `|` terminal payloads OWN what they carry (a scalar owns itself; substring
// allocates a fresh copy) with zero borrow machinery. Cheap views wait on the
// borrow-obligation surface; `split` (many parts) waits on the store.
//
// Parse the string's text as a base-10 integer.
~pub tor parse-int { s: *String<view|instance> }
| ok i64
| err string// Build a NEW owned String from a base-10 integer (the int→string mirror of
// parse-int). Owned return, so it carries its own ownership obligation.
~pub tor from-int { n: i64 }
| ok *String<view!>
| err string// Does the string contain `needle`?
~pub tor contains { s: *String<view|instance>, needle: string }
| yes
| no// First index of `needle`, or not-found.
~pub tor index-of { s: *String<view|instance>, needle: string }
| found usize
| not-found// Extract [start, end) as a NEW owned String<view!> (a fresh copy, not a
// borrow into `s`). The result carries its own ownership obligation.
~pub tor substring { s: *String<view|instance>, start: usize, end: usize }
| ok *String<view!>
| err stringsplit
koru_std/string.kz:210// Split — effect-stream over separated pieces
//
// Split a raw byte-slice on a separator, firing `! piece` per piece DURING the
// call (the read-lines streaming shape, one level down: `! line` is split-on-\n
// of a file; this is split-on-<sep> of a slice). Operates on []const u8 — the
// shape read-lines yields and regex named-groups capture — not a *String handle,
// so it composes directly with line/capture without wrapping. Pieces are
// BORROWED views into `s` (no allocation, no ownership), valid for the body.
//
// This is the naive-phase answer to variable-arity parsing: split into pieces,
// match each whole piece with the anchored `regex:match`. The principled
// alternative — a global/streaming regex (`scan`) that finds repeated matches
// in place — is pinned (640_011) and waits on the engine's unanchored-search
// "later cut"; split needs no engine work.
~pub tor split { s: string, sep: string }
! piece string
| done usize// Write operations - require instance
~pub tor append { s: *String<instance>, text: string }
| ok
| err string// Single-byte append — TUI key input (todo rename): `k.ch` is a u8, not a
// string literal. Pin 610_022. Without this, every keystroke would need a
// one-char String allocation just to call `append`.
~pub tor append-char { s: *String<instance>, ch: u8 }
| ok
| err string// A String's `data` slice IS its allocation record — there is no capacity
// field, which is why every mutator above reallocs. The two shrinking tors
// below must respect that too: editing `len` behind the allocator's back
// orphans the block, because std.mem.Allocator refuses to act on a
// zero-length slice in BOTH directions —
//
// free(s.data) -> `if (bytes_len == 0) return;` frees nothing
// realloc(s.data, n) -> `if (old_mem.len == 0)` allocates fresh and
// never releases the old pointer
//
// so a shrink-to-zero loses the buffer whether it is disposed or appended
// to next. Pinned as 610_023 (clear) and 610_024 (pop-char). Found by
// kopium's chat pane leaking one draft line per message sent.
//
// Drop the last byte (backspace). Empty stays empty — `| ok` either way so
// a held key doesn't force an err arm.
~pub tor pop-char { s: *String<instance> }~pub tor clear { s: *String<instance> }