Skip to content

Add allows ref struct constraint to TBufferWriter for .NET 9+#448

Open
juho-hanhimaki wants to merge 1 commit into
Cysharp:mainfrom
juho-hanhimaki:allows-ref-struct
Open

Add allows ref struct constraint to TBufferWriter for .NET 9+#448
juho-hanhimaki wants to merge 1 commit into
Cysharp:mainfrom
juho-hanhimaki:allows-ref-struct

Conversation

@juho-hanhimaki

@juho-hanhimaki juho-hanhimaki commented Jun 19, 2026

Copy link
Copy Markdown

Resolves #447

Overview
This PR adds the C# 13 allows ref struct anti-constraint to the TBufferWriter generic parameter across MemoryPackWriter and related core interfaces for .NET 9+. This enables users to implement custom buffer writers as ref structs, unlocking zero-allocation serialization for native and unmanaged memory arenas.

The Problem Solved
Implementing IBufferWriter<byte> normally forces a heap allocation for a MemoryManager<T> because GetMemory() must return a Memory<byte>. Since MemoryPackWriter strictly uses GetSpan(), this allocation is wasted. By allowing a ref struct buffer writer, consumers can skip the heap allocation entirely and throw NotSupportedException in their GetMemory() implementation.

Implementation Details & CS9050 Workaround
During implementation, updating MemoryPackWriter<TBufferWriter> to store the TBufferWriter as a ref field results in compiler error CS9050: A ref field cannot refer to a ref struct. Even though MemoryPackWriter itself is a ref struct, C# currently forbids declaring a ref T field if T is an anti-constraint.

To bypass this safely while maintaining zero-allocation performance, the internal field was mapped to a ref byte:

#if NET9_0_OR_GREATER
    ref byte bufferWriterRef;
    ref TBufferWriter bufferWriter => ref Unsafe.As<byte, TBufferWriter>(ref bufferWriterRef);
#endif

Because MemoryPackWriter is a ref struct, the GC's tracking of ref byte is identical to tracking ref TBufferWriter. The memory is guaranteed not to escape the stack or pinned context.

Breaking Changes

  • Older Frameworks: This change is wrapped in #if NET9_0_OR_GREATER compilation directives. Existing .NET 7 and .NET 8 users are completely unaffected.
  • .NET 9 Users: This introduces a source-breaking change for custom formatters compiled against .NET 9. Anyone who has written a custom formatter by inheriting from MemoryPackFormatter<T> or using [MemoryPackOnSerializing] / [MemoryPackOnSerialized] with a TBufferWriter parameter will need to add the allows ref struct constraint to their method signatures to match the updated base class/interface.
  • Source Generator: The MemoryPack Source Generator does not require updates. It emits explicit interface implementations (e.g., static void IMemoryPackable<T>.Serialize<TBufferWriter>(...)), which implicitly inherit the constraints from the interface.

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.

Proposal: Support ref struct buffer writers via allows ref struct constraint (.NET 9+)

1 participant