Skip to content

Make the CopyTo and WriteTo destination parameters scoped - #9

Merged
neuecc merged 1 commit into
mainfrom
scoped-destination
Oct 30, 2026
Merged

neuecc merged 1 commit into
mainfrom
scoped-destination

Conversation

@neuecc

@neuecc neuecc commented Oct 30, 2026

Copy link
Copy Markdown
Member

Reported from the MessagePack for C# v4 work against 1.3.1: on net9+, passing stack memory to IReadBuffer.CopyTo or ArrayPoolListWriteBuffer.WriteTo fails to compile, so MessagePack had to work around it in two places.

Problem

Both methods only copy into destination. Without scoped, though, the compiler must assume that a ref struct receiver could store the span in itself. So passing a stackalloc'd span fails with CS8350 whenever the buffer can outlive the call. This was reproduced for:

  • any buffer called through the generic ref TReadBuffer (allows ref struct) path, which checks against the interface member's signature
  • a concrete ReadOnlySpanReadBuffer / ReadOnlySequenceReadBuffer local built over heap memory
  • ArrayPoolListWriteBuffer.WriteTo on a local with heap scratch, or on a ref parameter

Change

The following now take scoped Span<byte> destination:

  • IReadBuffer.CopyTo
  • all four implementations, plus their private CopyToSlow helpers (a scoped span cannot be passed on to an unscoped parameter of the same ref struct)
  • ArrayPoolListWriteBuffer.WriteTo and CompatibleArrayPoolListWriteBuffer.WriteTo

The doc comments and the README (interface listing and the custom-implementation contract table) note the new rule.

Audit of the rest of the public surface

Parameter Decision
ReadOnlySpanReadBuffer(ReadOnlySpan), SpanWriteBuffer(Span), ReadOnlySequenceReadBuffer(ReadOnlySpan), the scratch parameters, BufferSegments ctor Stored in a field, so they must stay unscoped
ReadOnlySequenceReadBuffer(scoped in ReadOnlySequence<byte>, ...) Already scoped (#7)
TryGetSpan / TryGetNext out parameters out is implicitly scoped
CompatibleReadOnlySequenceReadBuffer(in ReadOnlySequence<byte>) Plain struct, cannot capture a reference; scoped would change nothing
GetReference extension No span arguments, no issue

Compatibility (please review)

  • Source: a ref struct that implements IReadBuffer without scoped on CopyTo now fails with CS8987. The fix is to add scoped. Plain struct implementations are not affected (verified). Since custom implementations are a documented extension point, this probably warrants a minor version bump rather than 1.3.2.
  • Binary: unchanged. scoped is encoded as ScopedRefAttribute on the parameter, and the IL signature is the same.

Tests

  • New ScopedDestinationTest passes stackalloc'd spans to CopyTo on all four read buffers (generic and concrete, including the segment-straddling CopyToSlow path), and to WriteTo on a local and a ref parameter (scratch plus pooled segments).
  • With scoped removed, the file fails to compile with CS8350 at all five call shapes (verified locally).
  • 158/158 pass locally.

🤖 Generated with Claude Code

IReadBuffer.CopyTo and ArrayPoolListWriteBuffer.WriteTo only copy into their
destination, but without `scoped` the compiler must assume a ref struct
receiver could keep it. Passing stack memory then failed with CS8350 whenever
the buffer outlived the call: a buffer built over heap memory, one reached by
ref, or any buffer through the generic `allows ref struct` path, which goes by
the interface member's signature.

The interface member, all four implementations, their private CopyToSlow
helpers and both WriteTo overloads now take `scoped Span<byte>`. Parameters
that constructors store (scratch, the span inputs) stay unscoped.

A ref struct that implements IReadBuffer must now declare the parameter
`scoped` too, or the compiler reports CS8987. Plain struct implementations
and compiled binaries are unaffected, since `scoped` is only an attribute on
the parameter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@neuecc
neuecc merged commit 63d3cfb into main Oct 30, 2026
7 checks passed
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.

1 participant