Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A ref struct is a custom value type in C# that is strictly constrained to be allocated only on the execution stack. The compiler enforces a rigorous set of rules to guarantee that instances of a ref struct can never escape to the managed heap, preventing them from being boxed, captured by closures, or embedded within heap-allocated objects.
public ref struct StackOnlyData
{
    public int Id;
    public ReadOnlySpan<char> Buffer;
}
Because standard struct types can be promoted to the heap (e.g., when boxed, when used as a field in a class, or when captured in an asynchronous state machine), the ref struct modifier was introduced to provide a compiler-enforced guarantee of stack-only semantics.

Compiler Constraints

To maintain the stack-only guarantee, the Roslyn compiler enforces the following structural and behavioral restrictions on any ref struct:
  • No Boxing: A ref struct cannot be cast to object, dynamic, or ValueType.
  • Interface Restrictions: While a ref struct can implement interfaces (as of C# 13), it cannot be cast or boxed directly to the interface type. It can only participate in interface dispatch through generic methods utilizing the allows ref struct anti-constraint.
  • Field Restrictions: A ref struct cannot be declared as a field within a class or a standard struct. It can only be a field within another ref struct.
  • No Closures: A ref struct cannot be captured by lambda expressions or local functions.
  • Asynchronous State Machines: A ref struct cannot be declared as a local variable in an async method if it is held across an await boundary, because the compiler rewrites async methods into state machine objects that reside on the heap.
  • Iterators: A ref struct cannot be held across a yield return boundary within an iterator method. As of C# 13, they are permitted as local variables in iterators as long as their lifetime does not span across the yield statement, which would require lifting them into a heap-allocated state machine.

ref Fields (C# 11+)

A ref struct is the only type in C# permitted to declare ref fields. A ref field stores a managed pointer (an interior reference) to data, rather than the data itself. This restriction exists for lifetime safety: it prevents heap-allocated objects from holding pointers to stack-allocated variables. If a heap object held a reference to a stack variable, that reference would become a dangling pointer as soon as the stack frame unwinds.
public ref struct PointerWrapper
{
    // Stores a reference to an integer, not the integer value
    public ref int NumberRef;
    
    // Stores a read-only reference to an integer
    public ref readonly int ReadOnlyNumberRef;
}

Generic Anti-Constraints (C# 13+)

Historically, a ref struct could not be used as a generic type argument because generic types do not guarantee stack-only allocation. Starting in C# 13, a ref struct can be passed as a generic type argument if the generic parameter explicitly declares the allows ref struct anti-constraint. This forces the generic type or method itself to adopt ref struct allocation rules.
// The 'allows ref struct' constraint permits T to be a ref struct
public ref struct GenericStackWrapper<T> where T : allows ref struct
{
    public T Data;
}

// Combining interface constraints with the anti-constraint
public void ProcessItem<T>(T item) where T : IDisposable, allows ref struct
{
    item.Dispose();
}

readonly ref struct

A ref struct can be combined with the readonly modifier. This enforces immutability on the struct’s fields while maintaining the stack-only allocation constraints. In a readonly ref struct, all standard fields must be marked readonly. For ref fields, the field must be declared with the readonly modifier (readonly ref T). This ensures the reference itself cannot be reassigned to point to a different memory location. It does not require the referent (the underlying data) to be immutable, though you can optionally add readonly to the referent type (readonly ref readonly T) if strict deep immutability is required.
public readonly ref struct ImmutableStackData
{
    public readonly int Length;
    
    // The reference cannot be reassigned, but the target integer CAN be modified
    public readonly ref int MutableTarget;
    
    // Neither the reference nor the target integer can be modified
    public readonly ref readonly int ImmutableTarget;
    
    public ImmutableStackData(int length, ref int mutableTarget, ref readonly int immutableTarget)
    {
        Length = length;
        MutableTarget = ref mutableTarget;
        ImmutableTarget = ref immutableTarget;
    }
}
Master C# with Deep Grasping Methodology!Learn More