Skip to content

feat: FixedSizeValueStringBuilder (closes stackalloc-based version - #315

Merged
linkdotnet merged 4 commits into
mainfrom
feat/fixed-size-value-string-builder
Sep 19, 2026
Merged

linkdotnet merged 4 commits into
mainfrom
feat/fixed-size-value-string-builder

Conversation

@linkdotnet

Copy link
Copy Markdown
Owner

Fixes #264

Copilot AI lite review requested due to automatic review settings September 19, 2026 18:57
@linkdotnet
linkdotnet force-pushed the feat/fixed-size-value-string-builder branch from 19908f8 to 74efa03 Compare September 19, 2026 19:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

A compile-blocking handler issue and unresolved implementation and documentation findings remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 2 Medium severity

Open (3)
What changed in this PR

Adds a fixed-capacity, stack-backed FixedSizeValueStringBuilder with overflow handling, interpolation support, tests, benchmarks, and documentation.

Changes:

  • Implements fixed-size append and formatting behavior.
  • Adds migration to the growable builder.
  • Adds tests, benchmarks, documentation, and changelog updates.
File Reviewed changes and final findings
tests/​LinkDotNet.StringBuilder.UnitTests/​FixedSizeValueStringBuilder.Tests.cs Core behavior tests.
tests/​LinkDotNet.StringBuilder.UnitTests/​FixedSizeValueStringBuilder.InterpolatedStringHandler.Tests.cs Interpolated-string handler tests.
tests/​LinkDotNet.StringBuilder.Benchmarks/​StackAllocVsRentBenchmark.cs Stack versus pooled allocation benchmark.
tests/​LinkDotNet.StringBuilder.Benchmarks/​PaddingBenchmark.cs Padding benchmark.
tests/​LinkDotNet.StringBuilder.Benchmarks/​FormatComparisonBenchmark.cs Formatting comparison benchmark.
tests/​LinkDotNet.StringBuilder.Benchmarks/​FixedSizeBenchmark.cs Fixed-size performance benchmark.
src/​LinkDotNet.StringBuilder/​ValueStringBuilder.cs Buffer-adopting constructor support.
src/​LinkDotNet.StringBuilder/​FixedSizeValueStringBuilder.InterpolatedStringHandler.cs Critical (1): the default argument creates an ambiguous overload call; make its target type explicit.
src/​LinkDotNet.StringBuilder/​FixedSizeValueStringBuilder.cs Moderate (2): indexer exposes unwritten buffer contents.
Moderate (3): unconstrained generic ISpanFormattable checks can box value types.
Moderate (1): formatted IFormattable values can lose their supplied format.
README.md Nit (1): scope the zero-allocation heading to the backing buffer or pooled-rent behavior.
docs/​site/​articles/​toc.yml Documentation navigation update.
docs/​site/​articles/​known_limitations.md Updated limitations.
docs/​site/​articles/​fixed_size.md Nit (1): qualify allocation guarantees.
Nit (1): handle overflow before moving to the growable builder.
Nit (1): document interpolated-handler per-part behavior.
docs/​site/​articles/​exceptions_and_edge_cases.md Moderate (1): align indexer bounds documentation with the implementation.
docs/​site/​articles/​concepts.md Documentation metadata update.
docs/​site/​articles/​comparison.md Nit (1): do not claim capacity checks were removed.
docs/​site/​articles/​best_practices.md Usage recommendations.
docs/​site/​articles/​additional_members.md Nit (1): correct the Rune overload documentation for span values.
CHANGELOG.md Release notes update.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

/// <returns><see langword="true"/> if it fit; otherwise, <see langword="false"/>, which makes the compiler
/// skip the rest of the interpolated string.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AppendFormatted<T>(T value) => AppendFormatted(value, default);
Comment thread src/LinkDotNet.StringBuilder/FixedSizeValueStringBuilder.cs Outdated
Comment on lines +337 to +341
// The cast to ISpanFormattable reads like a box, but because the interface method is invoked directly on the
// cast expression the JIT emits a constrained call and elides the allocation for value types. Hoisting it into
// an ISpanFormattable local, or routing it through a T : ISpanFormattable helper, would box for real and break
// this type's zero-allocation guarantee. Same shape the BCL uses in DefaultInterpolatedStringHandler.
if (value is ISpanFormattable)
@linkdotnet
linkdotnet force-pushed the feat/fixed-size-value-string-builder branch from 4cba116 to 288287a Compare September 19, 2026 19:17
@linkdotnet
linkdotnet requested a balanced review from Copilot September 19, 2026 19:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +257 to +259
buffer = default;
bufferPosition = 0;
overflowed = true;
Comment on lines +98 to +109
public bool AppendFormatted<T>(T value) => Builder.TryAppendFormatted(value, default);

/// <summary>
/// Appends a formatted value to the handler.
/// </summary>
/// <param name="value">The value to format.</param>
/// <param name="format">The format string.</param>
/// <typeparam name="T">The type of the value.</typeparam>
/// <returns><see langword="true"/> if it fit; otherwise, <see langword="false"/>, which makes the compiler
/// skip the rest of the interpolated string.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool AppendFormatted<T>(T value, string? format) => AppendFormatted(value, format.AsSpan());
Comment on lines +351 to +358
if (value is ISpanFormattable)
{
if (overflowed)
{
return false;
}

if (!((ISpanFormattable)value).TryFormat(buffer[bufferPosition..], out var written, format, null))
Comment thread CHANGELOG.md

### Added

- `FixedSizeValueStringBuilder`: a non-growing `ref struct` string builder backed by a caller-supplied buffer that never allocates on the heap. Appends are atomic and the first one that does not fit latches `Overflowed`, which `ClearOverflow` resets.
Comment thread README.md Outdated
```
Note that this will prevent you from returning `stringBuilder` or assigning it to an `out` parameter.

### Guaranteed zero allocation with `FixedSizeValueStringBuilder`
Comment on lines +46 to +47
Reading members never throw either: `AsSpan()`, `ToString()` and the indexer all see only the characters that were
actually written, and `TryCopyTo` returns `false` rather than throwing when the destination is too small.
Comment thread docs/site/articles/fixed_size.md Outdated
Comment on lines +209 to +211
Both builders would otherwise write into the same memory, so the move **consumes the source**. What is left behind is
an empty builder with `Capacity` of zero and `Overflowed` set to `true`. Reading it is safe and any further append is
a no-op, so a stale use cannot corrupt the buffer its new owner is writing into:
namespace LinkDotNet.StringBuilder;

/// <summary>
/// A string builder backed by a fixed-size, caller-supplied buffer which never grows and never allocates on the heap.
Comment thread tests/LinkDotNet.StringBuilder.Benchmarks/FixedSizeBenchmark.cs Outdated
@linkdotnet
linkdotnet force-pushed the feat/fixed-size-value-string-builder branch from 6c9593a to 09526fb Compare September 19, 2026 19:39
@linkdotnet
linkdotnet merged commit 1e75b5d into main Sep 19, 2026
1 check passed
@linkdotnet
linkdotnet deleted the feat/fixed-size-value-string-builder branch September 19, 2026 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

stackalloc-based version

2 participants