C# Ref Structs and Compilation Restrictions
A ref struct in C# is a specialized value type designed
for high-performance memory management that the compiler guarantees will
always reside exclusively on the execution stack. This article covers
what ref struct types are, why they exist, and the
exhaustive list of compiler-enforced restrictions that prevent them from
escaping to the managed heap.
What is a Ref Struct?
Introduced in C# 7.2, a ref struct is declared using the
ref modifier before the struct keyword. Its
primary purpose is to enable safe, zero-allocation memory access
patterns, most notably utilized by types like Span<T>
and ReadOnlySpan<T>.
Because a ref struct may contain internal references
(managed pointers) to stack memory, allowing it to move to the managed
heap would risk memory corruption, dangling pointers, and invalid memory
access after the stack frame unwinds. To guarantee memory safety, the C#
compiler enforces strict rules that keep instances confined to the
current thread's stack.
Key Compilation Restrictions
The compiler enforces several restrictions on ref struct
types to ensure they are never promoted to the heap:
1. No Boxing Allowed
A ref struct cannot be converted to object,
System.ValueType, or any interface type. Boxing inherently
allocates an object on the managed heap, which directly violates the
stack-only guarantee.
2. Cannot Be Array Elements
You cannot declare an array of a ref struct (for
example, Span<int>[] is invalid). In .NET, arrays are
reference types stored on the heap, which would force the elements onto
the heap as well.
3. Cannot Be Fields in Non-Ref Types
A ref struct cannot be a field of a normal
class or a standard struct. It can only be
declared as a field inside another ref struct. Placing a
ref struct inside a class or standard struct could allow it
to be stored on the heap.
4. Cannot Be Captured in Lambdas or Local Functions
You cannot capture a ref struct variable inside a lambda
expression, anonymous method, or local function. Closures generate
compiler-created classes on the heap to preserve state, which would
cause the captured variables to escape the stack.
5. Cannot Be Used Across Async/Await Boundaries
A ref struct cannot be used inside async
methods. The C# compiler transforms async methods into
state machine classes allocated on the heap, which would cause any local
variables to be stored as fields in that heap object.
6. Cannot Be Used in Iterator Blocks
You cannot use a ref struct inside an iterator method
that uses yield return. Similar to async methods, iterators
are compiled into heap-allocated state machines to maintain execution
state between iterations.
7. Restricted Generic Usage
By default, a ref struct cannot be used as a type
argument for generic classes, interfaces, or methods. (Starting in C#
13, generic parameters can opt in to allow ref struct types
by specifying the allows ref struct anti-constraint, but
general unconstrained generic usage remains prohibited).
8. Cannot Be Declared as Static or Global Variables
A ref struct cannot be used as a static field in any
type, nor can it be used in static state. Static fields persist for the
lifetime of the application domain and reside on the heap.