The Problem
Currently, when using MemoryPack with highly optimized, custom memory allocators (like native/unmanaged memory arenas), users are forced to implement IBufferWriter<byte>. The interface requires implementing GetMemory(), which returns a Memory<byte>. Wrapping a raw pointer in a Memory<byte> necessitates allocating a heap-based MemoryManager<T> per buffer.
Since MemoryPackWriter only ever uses GetSpan() and never actually calls GetMemory(), this forces a completely unnecessary heap allocation just to satisfy the interface constraints, which goes against the high-performance, zero-allocation philosophy of MemoryPack.
Proposed Solution
With the introduction of C# 13 and .NET 9, we can utilize the allows ref struct anti-constraint.
By applying this constraint to the TBufferWriter generic parameter in MemoryPackWriter<TBufferWriter> and its related core interfaces (conditionally under #if NET9_0_OR_GREATER), developers could pass ref struct implementations of IBufferWriter<byte>. This would allow custom writers to throw NotSupportedException in GetMemory() and rely purely on GetSpan(), completely avoiding heap allocations for native memory buffers.
Impact and Considerations
- Performance: Unlocks true zero-allocation custom buffer writers for unmanaged memory.
- Backwards Compatibility: We can wrap this in
#if NET9_0_OR_GREATER directives so .NET 7 and 8 users are unaffected.
- Compiler Limitations: There is a known limitation in C# 13 (CS9050) where a
ref field cannot refer to a ref struct anti-constraint generic parameter. This would require an internal workaround using ref byte and Unsafe.As.
- Breaking Changes (.NET 9 only): This would be a source-breaking change for .NET 9 users who have written custom formatters inheriting from
MemoryPackFormatter<T>, as they would need to propagate the allows ref struct constraint. (The MemoryPack source generator itself implicitly handles constraints via explicit interface implementations).
I have a working implementation for this on a fork and can submit a PR if this proposal is accepted.
The Problem
Currently, when using
MemoryPackwith highly optimized, custom memory allocators (like native/unmanaged memory arenas), users are forced to implementIBufferWriter<byte>. The interface requires implementingGetMemory(), which returns aMemory<byte>. Wrapping a raw pointer in aMemory<byte>necessitates allocating a heap-basedMemoryManager<T>per buffer.Since
MemoryPackWriteronly ever usesGetSpan()and never actually callsGetMemory(), this forces a completely unnecessary heap allocation just to satisfy the interface constraints, which goes against the high-performance, zero-allocation philosophy ofMemoryPack.Proposed Solution
With the introduction of C# 13 and .NET 9, we can utilize the
allows ref structanti-constraint.By applying this constraint to the
TBufferWritergeneric parameter inMemoryPackWriter<TBufferWriter>and its related core interfaces (conditionally under#if NET9_0_OR_GREATER), developers could passref structimplementations ofIBufferWriter<byte>. This would allow custom writers to throwNotSupportedExceptioninGetMemory()and rely purely onGetSpan(), completely avoiding heap allocations for native memory buffers.Impact and Considerations
#if NET9_0_OR_GREATERdirectives so .NET 7 and 8 users are unaffected.reffield cannot refer to aref structanti-constraint generic parameter. This would require an internal workaround usingref byteandUnsafe.As.MemoryPackFormatter<T>, as they would need to propagate theallows ref structconstraint. (The MemoryPack source generator itself implicitly handles constraints via explicit interface implementations).I have a working implementation for this on a fork and can submit a PR if this proposal is accepted.