C
C#10mo ago
CatSandwich

❔ Benefit of ref structs

Hey folks! I've been trying for a bit to understand the purpose of using a ref struct over a regular struct. I understand that it can only live on the stack, which is enforced via a set of usage rules, but regular structs could do that too if used properly. I asked my good old friend chatgpt to help me understand it, and the answer I got was basically that the compiler is able to optimize the usage of ref structs better given its restrictions. Is this accurate? Is it the only benefit? I'm used to concepts like the out keyword where its usage restrictions (must be populated by return, cannot be read) come as a tradeoff for visible benefits (inline variable declaration in a method call), so I guess I was expecting something similar in this case, specifically something that can only be done with ref structs.
26 Replies
JakenVeina
JakenVeina10mo ago
the truth is that ref structs exist because of Span<T> they had to invent a new language mechanic to make Span<T> a thing, and that was ref struct
CatSandwich
CatSandwich10mo ago
Why did Span<T> need the restrictions?
JakenVeina
JakenVeina10mo ago
one of the big use-cases of Span<T> was to be able to provide windows into arrays and in particular stackalloc'd arrays
CatSandwich
CatSandwich10mo ago
Could that not have worked without the concept of ref structs?
JakenVeina
JakenVeina10mo ago
not really what you can do with the advent of ref struct and Span<T> is
var numbers = stackalloc int[5];
var numbers = stackalloc int[5];
numbers here becomes a Span<T> or to REALLY highlight where the optimization potential is....
var numbers = stackalloc int[length];
var numbers = stackalloc int[length];
even if you KNOW this allocation is small, and you're only going to need it for a short while before Span<T> and stackalloc this would HAVE to be allocated on the heap and put pressure on the GC
CatSandwich
CatSandwich10mo ago
I'll have to take some time to digest that later
JakenVeina
JakenVeina10mo ago
if you have a big hot path that does something like this, the best you could do before was implement some kind of array pooling I.E. allocate one array/List<T> ahead of time (or several), and reuse it stackalloc lets you swap out a complex heap allocation, which also creates comples GC work for later, with the equivalent of...
stackPointer += length;
stackPointer += length;
CatSandwich
CatSandwich10mo ago
Could Memory<T> not have worked in its place? var memory = stackalloc...
JakenVeina
JakenVeina10mo ago
it's the equivalent of declaring length number of int local method variables, and then Span<T> just gives you an API over that nope, Memory<T> is not a ref struct and therefore it can get captured and boxed onto the heap
CatSandwich
CatSandwich10mo ago
Sure but pretending ref struct was never invented Could stackalloc not have worked with memory? Why does ref struct allow stackalloc?
JakenVeina
JakenVeina10mo ago
not really
jcotton42
jcotton4210mo ago
b/c ref structs cannot be heaped
JakenVeina
JakenVeina10mo ago
Span<T> does work very similarly to Memory<T>
jcotton42
jcotton4210mo ago
it's in part b/c the GC doesn't like dealing with ref fields (which ref structs can contain)
JakenVeina
JakenVeina10mo ago
it's essentially got a reference to the memory inside itself but when you copy a Memory<T> you're not actually copying the memory it refers to even though Memory<T> itself is passed by value, it's value consists of a reference so, if Memory<T> were to refer to a stackalloc'd chunk of memory what could happen is that the Memory<T> gets captured and put on the heap that DOES NOT cause the stackalloc'd memory to be copied to the heap
jcotton42
jcotton4210mo ago
also this kind of question is better suited to #advanced or even #allow-unsafe-blocks than #help
JakenVeina
JakenVeina10mo ago
so, now, the method that allocated the memory can finish, thus de-allocating it but the heap still has that Memory<T> which now refers to garbage memory
CatSandwich
CatSandwich10mo ago
Yeah I'm not understanding it yet, but I'll circle back to digest it later Thanks folks!
JakenVeina
JakenVeina10mo ago
mm-kay
CatSandwich
CatSandwich10mo ago
split attention right now unfortunately Okay it clicked Thanks again!
reflectronic
reflectronic10mo ago
the key thing is that a ref struct can have a ref field
ref struct MyStruct
{
public ref int X;
}
ref struct MyStruct
{
public ref int X;
}
X is a field that is a reference to an int somewhere in memory. a pointer, if you will this is only allowed in ref structs. Span<T>, for example, is internally implemented as:
public readonly ref struct Span<T>
{
/// <summary>A byref or a native ptr.</summary>
internal readonly ref T _reference;
/// <summary>The number of elements this Span contains.</summary>
private readonly int _length;
public readonly ref struct Span<T>
{
/// <summary>A byref or a native ptr.</summary>
internal readonly ref T _reference;
/// <summary>The number of elements this Span contains.</summary>
private readonly int _length;
(this is copied directly from the source)
CatSandwich
CatSandwich10mo ago
right that makes sense
333fred
333fred10mo ago
If you really want to understand the power of ref structs, go play with rust for a while ref structs are basically the C# compiler implementing a borrow checker
JakenVeina
JakenVeina10mo ago
a borrow-checker that only supports borrowing on the stack, essentially?
333fred
333fred10mo ago
Essentially
Accord
Accord10mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.