hanki

string_builder

stdlib/string_builder.hk: the StringBuilder mutable text buffer.

StringBuilder is a runtime-managed, non-sendable native resource (HANKI.md §4): the text mirror of BytesBuilder, and the O(n) way to accumulate a string one piece at a time. string is immutable, and a loop that rebound acc = acc.concat(x) pays O(n^2) on any tier that cannot grow the left operand in place. A buffer accumulates in place on both tiers, and says so at the call site, with no reliance on an invisible optimization.

finish! is total: every pushed piece is a string and so already valid UTF-8, and the join of valid UTF-8 is valid UTF-8, and there is no decode step and no Result. That is the difference from assembling text through a BytesBuilder, whose bytes.to_string can fail.

Every operation is an action (!) with an empty effect row, for the reasons spelled out in bytes_builder.hk: an in-memory mutation commits no world capability, and it is not substitutable, and pure code may not perform it. A def that owns its buffer and lets only the finished string escape can be @encapsulated (HANKI.md §6) and stay pure.

The primitive operations are @intrinsic; lowering emits Op::HostCall and the scheduler dispatches into the registered Rust closure (bytecode), or the AOT C-ABI shim mutates the boxed buffer in place.

StringBuilder

StringBuilder, or str.StringBuilder, is a runtime-managed native resource handle (HANKI.md §4): live native state the per-actor memory manager owns, released when the last handle drops. It is single-owner - never copied, moved across a send - and has no fields of its own, so its methods are its whole surface. A handle is minted by an API that opens one; it is never constructed.

impl StringBuilder

new!

def new!() -> StringBuilder

A new, empty buffer.

b = StringBuilder.new!()
b.finish!() => ""

push!

def push!(self, s: string) -> ()

Append s to the end of the buffer.

b = StringBuilder.new!()
b.push!("hel")
b.push!("lo")
b.finish!() => "hello"

push_display!

def push_display!<T: Display>(self, v: T) -> ()

Append v rendered through its Display impl: push!(v.to_string()), spelled once, which leaves an accumulation loop over non-string values a single call.

b = StringBuilder.new!()
b.push!("n=")
b.push_display!(42)
b.finish!() => "n=42"

finish!

def finish!(self) -> string

The text accumulated so far. Non-consuming: the buffer is left intact and may go on growing, and a later finish! returns this text plus whatever was pushed in between.

b = StringBuilder.new!()
b.push!("Hi")
b.push!("!")
b.finish!() => "Hi!"