Skip to content

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

@juho-hanhimaki

Description

@juho-hanhimaki

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions