I'm in the process of changing what was formerly a Class into a Struct, as part of an editor patch within Unity. I've read a lot of advice on using structs being "Don't allow mutable structs", due to poor copy behaviour resulting in modified copies, and being hard to track. As I understand, a result of being stack-based and having no data overhead.
However, I'd like to clarify that with the specific case. Is a boolean value okay to mutate within a struct, since the data size can never change? The particular boolean property could theoretically be modified with frequency, so if it's likely to cause memory problems I'll have to implement some other way to track that parameter that elsewhere.
Extra notes, in case of unexpected relevance:
The mantra "mutable structs are evil" is in itself a poor reflection on what the actual issue is. A more accurate version would be "secretly mutable structs in immutable locations are evil". Such locations include non-ref
properties or method returns, readonly
fields, or any non-variable expressions in general.
The common source of surprise for people is that they forget that structs and classes are different, and must be handled differently. It is not correct to say that structs are stack-based, rather they are "indirectionless". Instances of classes have their own identity ‒ they are passed around indirectly, via references. Instances of structs are values; they don't have their own identity, their identity is the identity of the variable they are stored in.
Contrary to the other answer, this code is not an issue:
myObject.MyStructProperty.MyBoolfield = true;
The compiler raises CS1612 in this situation, protecting you from the actual most common source of errors. It understands that this is (in case of mutating a property, most likely) a useless mutation of a temporary value, and since C# 7, using a ref
property can give you what you want anyway.
The actual issue are methods. C# didn't have readonly
methods prior to version 8, meaning the compiler would always have to assume that a method could potentially modify the value (having to make defensive copies for readonly
fields), yet still such methods have to be callable in all situations, since they as well may be useful just for their return value.
myObject.MyStructProperty.FlipBoolfield();
The compiler cannot warn you about this situation, since it doesn't know that FlipBoolfield
secretly mutates the value. If MyStructProperty
is non-ref
, the mutation happens on a temporary copy of the value, and the changes are lost.
All in all, simply don't mutate structs through methods. Mark all struct methods readonly
, but keep mutable properties and fields if you want to.
Since this is in the context of Unity, the engine actually uses a lot of mutable structs (and fields) everywhere, so you don't risk running into the error anyway.
Simply put, this is fine:
public struct DayDuration
{
public int Days;
}
This is not fine:
public struct DayDuration
{
public int Days;
// secret struct mutation
public void AddDays(int count)
{
Days += count;
}
}
This is fine again:
public struct DayDuration
{
public int Days;
// explicit ref parameter
public static void AddDays(ref DayDuration duration, int count)
{
duration.Days += count;
}
}
Or even better:
public struct DayDuration
{
public int Days;
}
public static class DayDurationExtensions
{
// amazing to use and warns against non-mutable locations
public static void AddDays(this ref DayDuration duration, int count)
{
duration.Days += count;
}
}
You also seem to be particularly confused about the "overhead" of structs/classes. Since structs lack additional indirection, accessing them is faster, as the CPU doesn't have to go through a reference, and the GC doesn't have to be invoked at all. Be aware however that assigning a struct instance (value) to a different location without ref
will copy all the data, so you won't get any memory optimization from it.