ValueStringBuilder Type
A stack-first string builder: writes into a caller-supplied Span{T}
and only rents from the shared ArrayPool{T} once that buffer
overflows. Call Dispose() once to return the rented array.
This is a ref struct, the same tradeoff that keeps the BCL's own
System.Text.ValueStringBuilder internal. Three ways it bites:
Caught by the compiler: storing it in a field, capturing it in a closure, or using it across a let! /do! . There is therefore no closure-taking helper API — a parameter typedValueStringBuilder byref -> 'R does not compile at all.Not caught: passing it by value. A copy shares the same backing array, so if either side grows or disposes, the other is left pointing at memory the pool may have handed to somebody else. This is why no member returns this : fluent chaining would copy.Not caught: a stack-allocated span outliving its frame. F# has no stackalloc expression, and a span built by hand fromNativePtr.stackalloc is invisible to escape analysis — returning one compiles silently. Allocate in the function that uses it, never in a loop.
It has a plain Dispose() and does not implement IDisposable, so
use does not apply — dispose from a try/finally. Dispose is idempotent.
When to use it: a hot path that builds one string per call and consumes it
immediately. Measured against StringBuilder from 16 to 1,048,576
characters (ValueStringBuilderBenchmarks): never slower, and a third to a half of the
allocations. When not to use it: anywhere the value must escape the building function
— which is most code, since it cannot be returned, stored, or passed — or anywhere not hot
enough to be worth the danger above. When in doubt use StringBuilder.
Example
The one supported shape — a local let mutable, plain statement calls, disposed in a
finally:
let describe (name: string) (count: int) =
let buffer = Array.zeroCreate<char> 64
let mutable sb = ValueStringBuilder(Span<char>(buffer))
try
sb.Append("name=")
sb.Append(name)
sb.ToString()
finally
sb.Dispose()
val string: value: 'T -> string
--------------------
type string = System.String
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
val char: value: 'T -> char (requires member op_Explicit)
--------------------
type char = System.Char
Record fields
| Record Field |
Description
|
Full Usage:
_arrayToReturnToPool
Field type: char[]
Modifiers: mutable |
|
|
|
Full Usage:
_pos
Field type: int
Modifiers: mutable |
|
Constructors
| Constructor |
Description
|
Full Usage:
ValueStringBuilder(initialCapacity)
Parameters:
int
-
The minimum initial capacity to rent.
Returns: ValueStringBuilder
|
Creates a builder backed entirely by a pooled array of at least initialCapacity characters, rented immediately from the shared ArrayPool{T}. Use this overload when there is no convenient stack buffer to hand in.
|
Full Usage:
ValueStringBuilder(initialBuffer)
Parameters:
Span<char>
-
The buffer to write into before growing into the pool.
Returns: ValueStringBuilder
|
Creates a builder that writes into initialBuffer first — for example
a
|
Instance members
| Instance member |
Description
|
Full Usage:
this.Append
Parameters:
string
-
The string to append; null is ignored.
|
Appends s. A null string is a no-op, matching StringBuilder.
|
|
Appends every character of value, growing into the shared pool if needed.
|
Full Usage:
this.Append
Parameters:
char
-
The character to append.
|
Appends a single character, growing into the shared pool if the buffer is full.
|
Full Usage:
this.AppendLine
|
Appends the platform newline sequence ( |
|
The characters written so far, as a read-only view over the live buffer (no copy).
Only valid until the next
|
Full Usage:
this.Clear
|
Resets the write position to zero without releasing the current buffer, so the same instance — and, if it already grew, the same rented array — can be reused to build another string. |
Full Usage:
this.Dispose
|
Returns the rented array (if any) to the shared pool. Safe to call more than once —
every call after the first is a no-op. This type is not IDisposable
(see the type-level remarks) — call this explicitly from a |
Full Usage:
this.Length
Returns: int
|
The number of characters written so far.
|
Full Usage:
this.TryCopyTo
Parameters:
Span<char>
-
The buffer to copy into.
charsWritten : byref<int>
-
Receives the number of characters copied, or 0 if the copy failed.
Returns: bool
true if destination was large enough; otherwise false.
|
Attempts to copy the characters written so far into destination, without allocating a string.
|
TDesu.FSharp