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.