# Struct Equality and SkipLocalsInit

I have recently learned an interesting behavior of the .NET runtime on equality from a [post](https://mastodon.social/@xoofx/113006624434544543) on Mastodon. The post points out that [RuntimeHelpers.Equals](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.runtimehelpers.equals?view=net-8.0) returns whether the input arguments' underlying memory are equal, which may not be true always, even when the values are equal.

Under the [hood](https://github.com/dotnet/runtime/blob/f61ca08d93fe06599f4566d6faf6f9da4cf05484/src/coreclr/classlibnative/bcltype/objectnative.cpp#L69) `RuntimeHelpers.Equals` uses `memcmp` to compare the memory representation of two objects.

## Padding

In C# structs may have paddings in the memory representation. For example, the following struct will have a padding on x64, so that the value is aligned to 8 bytes:

```csharp
struct MyType
{
    public short Id;
    public double Value;
}
```

In this sharplab.io [example](https://sharplab.io/#v2:C4LgTgrgdgPgAgJgAwFgBQiCM70FkCeAKvgA4CmABAG4CGANhJQLwVRkDuAFAJQUDeFAJIATCi0wAaCgDV6jMRSQA6AKwUAvgG50gqAGdyAY2BKAysBqGA1p1oMy3bWj3BIxigWLl0fdBX8UcADMFHoAFgD2YMBCwk4BgSHCERAARnSUsvZO6jho6EA=) the padding can be viewed with the help of the Inspect command:

| Id | Padding | Value |
|----|---------|-------|
| 01 00 | 00 00 00 00 00 00 | 00 00 00 00 00 00 00 E0 3F |

Notice, that replacing the `short` type of the *Id* field with `double` would results a value without padding:

| Id | Value |
|----|-------|
| 00 00 00 00 00 00 F0 3F | 00 00 00 00 00 00 00 E0 3F |

There are other ways to create `struct`s with paddings, for example by controlling the [struct layout](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute?view=net-8.0). The exact behavior of paddings depends on the processor architecture, the type of fields, reference types, etc.

For the purpose of this post the important fact is that `MyType` has a padding with the `short` *Id* field.

## SkipLocalsInit

[SkipLocalsInit](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.skiplocalsinitattribute?view=net-8.0) is an attribute well-explained in [this post](https://www.meziantou.net/csharp-9-improve-performance-using-skiplocalsinit.htm) by meziantou, and I have also previously explored it [in the context of array allocations](https://blog.ladeak.net/posts/skipinitlocal). 

This flag tells the JIT compiler to skip initializing local variables to their default value of 0. That means memory remains uninitialized, and it may show invalid values. To enable skipping locals initialization, one needs to apply the attribute (method, assembly, etc. level) and turn on `AllowUnsafeBlocks` compiler option.

With `MyType` when there is no value set explicitly for the `Id` or `Value` fields, they *may* take up a random value instead of being zeroed out, for example when being stack allocated. But the padding remains uninitialized *even* when the ctor of the type runs.

This [example in sharplab](https://sharplab.io/#v2:C4LgTgrgdgPgAgJgAwFgBQiCM65IARyYB0AStMAJYC2ApkQMID2VADhQDY1gDKXAbhQDGNAM4BudOgDaVRgBMInENwDWFFgBlGggIbsRASSgVgAXXQBZAJ4AVKyxp4+eiI4C8eKDQDuACgCUeADeeAZyeB6YADR4AGou7nhIRACseAC+EmjcLDpQADzWdg4AfHhUtvaiEXgiwDqCKnrs2nhFVVIIplkVxaJSSKZEYTWYPZUOIgND8eyuNckpWUYiDoLARNz1jb69VVOD/stQqzTrm9sqvs5zNEfodZDrbRM06EHoeF8EAMy1ABaMMDAUJyLLfX54OSMCAAI04cQSWXS6CAA=) shows that the padding part of the struct remains uninitialized.

## Equality

The interesting behavior can be achieved by combining the above two behaviors:

- enable `SkipLocalsInit`
- add a `struct` type with padding
- allocate two values of this type and set their fields to be equal
- test equality `RuntimeHelpers.Equals`

```csharp
[module: SkipLocalsInit]
var a = new MyType() { Id = 1, Value = 0.5 };
Span<MyType> c = stackalloc MyType[2]; // Needs to be >1
c[0].Id = 1;
c[0].Value = 0.5;
Console.WriteLine(a.Equals(c[0]));
Console.WriteLine(RuntimeHelpers.Equals(a, c[0]));
```

The above example prints `True False`. When the equality is tested by the regular `Equals` call, the `struct` types will perform a field-by-field comparison. Because all fields are initialized to the same value this operation results *true*. When `RuntimeHelpers.Equals` performs the comparison (structs become boxed) the underlying memory is different due to the uninitialized padding. Hence this operation returns *false*.

## Conclusion

While I find the above behavior interesting, I don't think regular LOB applications would face such situations where developers would need to worry about it. However, for libraries that are designed to solve high-performance or interop problems, custom `structs` and struct layouts with skipping locals initialization is more common. In such a case it is good to be aware of the above-described behavior.