Skip to content

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

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions