Why Are Mutable Structs "Evil"

Why are mutable structs “evil”?

Structs are value types which means they are copied when they are passed around.

So if you change a copy you are changing only that copy, not the original and not any other copies which might be around.

If your struct is immutable then all automatic copies resulting from being passed by value will be the same.

If you want to change it you have to consciously do it by creating a new instance of the struct with the modified data. (not a copy)

Why is it okay that this struct is mutable? When are mutable structs acceptable?

Actually, if you search for all classes containing BitVector in the .NET framework, you'll find a bunch of these beasts :-)

  • System.Collections.Specialized.BitVector32 (the sole public one...)
  • System.Web.Util.SafeBitVector32 (thread safe)
  • System.Web.Util.SimpleBitVector32
  • System.Runtime.Caching.SafeBitVector32 (thread safe)
  • System.Configuration.SafeBitVector32 (thread safe)
  • System.Configuration.SimpleBitVector32

And if you look here were resides the SSCLI (Microsoft Shared Source CLI, aka ROTOR) source of System.Configuration.SimpleBitVector32, you'll find this comment:

//
// This is a cut down copy of System.Collections.Specialized.BitVector32. The
// reason this is here is because it is used rather intensively by Control and
// WebControl. As a result, being able to inline this operations results in a
// measurable performance gain, at the expense of some maintainability.
//
[Serializable()]
internal struct SimpleBitVector32

I believe this says it all. I think the System.Web.Util one is more elaborate but built on the same grounds.

Why are C# structs immutable?

If this subject interests you, I have a number of articles about immutable programming at https://ericlippert.com/2011/05/26/atomicity-volatility-and-immutability-are-different-part-one/

I was just curious to know why structs, strings etc are immutable?

Structs and classes are not immutable by default, though it is a best practice to make structs immutable. I like immutable classes too.

Strings are immutable.

What is the reason for making them immutable and rest of the objects as mutable.

Reasons to make all types immutable:

  • It is easier to reason about objects that do not change. If I have a queue with three items in it, I know it is not empty now, it was not empty five minutes ago, it will not be empty in the future. It's immutable! Once I know a fact about it, I can use that fact forever. Facts about immutable objects do not go stale.

  • A special case of the first point: immutable objects are much easier to make threadsafe. Most thread safety problems are due to writes on one thread and reads on another; immutable objects don't have writes.

  • Immutable objects can be taken apart and re-used. For example, if you have an immutable binary tree then you can use its left and right subtrees as subtrees of a different tree without worrying about it. In a mutable structure you typically end up making copies of data to re-use it because you don't want changes to one logical object affecting another. This can save lots of time and memory.

Reasons to make structs immutable

There are lots of reasons to make structs immutable. Here's just one.

Structs are copied by value, not by reference. It is easy to accidentally treat a struct as being copied by reference. For example:

void M()
{
S s = whatever;
... lots of code ...
s.Mutate();
... lots more code ...
Console.WriteLine(s.Foo);
...
}

Now you want to refactor some of that code into a helper method:

void Helper(S s)
{
... lots of code ...
s.Mutate();
... lots more code ...
}

WRONG! That should be (ref S s) -- if you don't do that then the mutation will happen on a copy of s. If you don't allow mutations in the first place then all these sorts of problems go away.

Reasons to make strings immutable

Remember my first point about facts about immutable structures staying facts?

Suppose string were mutable:

public static File OpenFile(string filename)
{
if (!HasPermission(filename)) throw new SecurityException();
return InternalOpenFile(filename);
}

What if the hostile caller mutates filename after the security check and before the file is opened? The code just opened a file that they might not have permission to!

Again, mutable data is hard to reason about. You want the fact "this caller is authorized to see the file described by this string" to be true forever, not until a mutation happens. With mutable strings, to write secure code we'd constantly have to be making copies of data that we know do not change.

What are the things that are considered to make an object immutable?

Does the type logically represent something that is an "eternal" value? The number 12 is the number 12; it doesn't change. Integers should be immutable. The point (10, 30) is the point (10, 30); it doesn't change. Points should be immutable. The string "abc" is the string "abc"; it doesn't change. Strings should be immutable. The list (10, 20, 30) doesn't change. And so on.

Sometimes the type represents things that do change. Mary Smith's last name is Smith, but tomorrow she might be Mary Jones. Or Miss Smith today might be Doctor Smith tomorrow. The alien has fifty health points now but has ten after being hit by the laser beam. Some things are best represented as mutations.

Is there any difference on the way how memory is allocated and deallocated for mutable and immutable objects?

Not as such. As I mentioned before though, one of the nice things about immutable values is that something you can re-use parts of them without making copies. So in that sense, memory allocation can be very different.

Value Types, Immutability (Good) & Mutability (Evil) in .NET

The issue of mutability is not one of value vs reference type. There are examples for both. Take System.String as an example for an immutable class as well as your example of System.Drawing.Point as an example of a mutable struct.

Mutable vs. immutable is a design decision based on the usage of the type. Whether it's a reference or a value type is another design decision that is not dependent on the former.

C# Structs: Mutability versus Performance

There are two distinct usage cases for structs; in some cases, one wants a type that encapsulates a single value and behaves mostly like a class, but with better performance and a non-null default value. In such cases, one should use what I would call an opaque structure; MSDN's guidelines for structures are written on the assumption that this is the only usage case.

In other cases, however, the purpose of the struct is simply to bind some variables together with duct tape. In those cases, one should use a transparent struct which simply exposes those variables as public fields. There is nothing evil about such types. What's evil is the notion that everything should behave like a class object, or that everything should be "encapsulated". If the semantics of the struct are such that:

  1. There is some fixed set of readable members (fields or properties) which expose its entire state
  2. Given any set of desired values for those members, one can create an instance with those values (no combinations of values are forbidden).
  3. The default value of the struct should be to have all those members set to the default values of their respective types.

and any change to them would break the code which uses it, then there is nothing which future versions of the struct could possibly do which a transparent struct would not be able to do, nor is there anything that a transparent struct would allow which a future version of the struct would be able to prevent. Consequently, encapsulation imposes cost without adding value.

I would suggest that one should endeavor to, whenever practical, make all structs either transparent or opaque. Further, I would suggest that because of deficiencies in the way .net handles struct methods, one should avoid having opaque structures' public members modify this except perhaps in property setters. It is ironic that while MSDN's guidelines suggest one shouldn't use a struct for things that don't represent a "single value", in the common scenario where one simply wants to pass a group of variables from one piece of code to another, transparent structs are vastly superior to opaque structs or class types, and the margin of superiority grows with the number of fields.

BTW, with regard to the original question, I would suggest that it's useful to represent that your program may want to deal with two kinds of things: (1) a car, and (2) information related to a particular car. I would suggest that it may be helpful to have a struct CarState, and have instances of Car hold a field of type CarState. This would allow instances of Car to expose their state to outside code and possibly allow outside code to modify their state under controlled circumstances (using methods like

delegate void ActionByRef<T1,T2>(ref T1 p1, ref T2 p2);
delegate void ActionByRef<T1,T2,T3>(ref T1 p1, ref T2 p2, T3 p3);

void UpdateState<TP1>(ActionByRef<CarState, TP1> proc, ref TP1 p1)
{ proc(ref myState, ref p1); }
void UpdateState<TP1,TP2>(ActionByRef<CarState, TP1,TP2> proc, ref TP1 p1, ref TP2 p2)
{ proc(ref myState, ref p1, ref p2); }

Note that such methods offer most of the performance advantages of having the car's state be a mutable class, but without the dangers associated with promiscuous object references. A Car can let outside code may update a car's state via the above methods without allowing outside code to modify its state at any other time.

BTW, I really wish .net had a way of specifying that a "safe" struct or class should be considered as encapsulating the members of one or more of its constituents [e.g. so that struct which held a Rectangle called R and an String called Name could be regarded as having a fields X, Y, Width, and Height which alias the corresponding struct fields. If that were possible, it would greatly facilitate situations where a type needs to hold more state than previously expected. I don't think the present CIL allows for such aliasing in a safe type, but there's no conceptual reason it couldn't.

Immutability of structs

Structs should represent values. Values do not change. The number 12 is eternal.

However, consider:

Foo foo = new Foo(); // a mutable struct
foo.Bar = 27;
Foo foo2 = foo;
foo2.Bar = 55;

Now foo.Bar and foo2.Bar is different, which is often unexpected. Especially in the scenarios like properties (fortunately the compiler detect this). But also collections etc; how do you ever mutate them sensibly?

Data loss is far too easy with mutable structs.

Mutable struct vs. class?

First off, if you really want to save memory then don't be using a struct or a class.

byte[,,] blockTypes = new byte[16, 256, 16]; 
BlockMetaData[,,] blockMetadata = new BlockMetaData[16, 256, 16];

You want to tightly pack similar things together in memory. You never want to put a byte next to a reference in a struct if you can possibly avoid it; such a struct will waste three to seven bytes automatically. References have to be word-aligned in .NET.

Second, I'm assuming that you're building a voxel system here. There might be a better way to represent the voxels than a 3-d array, depending on their distribution. If you are going to be making a truly enormous number of these things then store them in an immutable octree. By using the persistence properties of the immutable octree you can make cubic structures with quadrillions of voxels in them so long as the universe you are representing is "clumpy". That is, there are large regions of similarity throughout the world. You trade somewhat larger O(lg n) time for accessing and changing elements, but you get to have way, way more elements to work with.

Third, "ID" is a really bad way to represent the concept of "type". When I see "ID" I assume that the number uniquely identifies the element, not describes it. Consider changing the name to something less confusing.

Fourth, how many of the elements have metadata? You can probably do far better than an array of references if the number of elements with metadata is small compared to the total number of elements. Consider a sparse array approach; sparse arrays are much more space efficient.

Are mutable structs copied when passed through methods via in parameters?

As mentioned in the comments, using in for parameters of mutable structs can create defensive copies if the runtime cannot guarantee that the passed instance is not modified. It may be hard to guarantee this if you call properties, indexers, or methods on that instance.

Ergo, whenever you do not intend to modify the instance in such, you should state this explicitly by making them readonly. This has the benefit of also causing compilation to fail if you attempt to modify the instance in them.

Note the placement of the readonly keyword in the following examples especially:

public struct Vec2
{
public float X, Y;

// Properties
public readonly float Length
{
get { return MathF.Sqrt(LengthSq); }
}
public readonly float LengthSq => X * X + Y * Y;

// Indexers (syntax the same for properties if they also have setter)
public float this[int index]
{
readonly get => index switch
{
0 => X,
1 => Y,
_ => throw ...
};
set
{
switch (index)
{
case 0: X = value; break;
case 1: Y = value; break;
default: throw ...
}
}
}

// Methods
public readonly override int GetHashCode() => HashCode.Combine(X, Y);
}

Now, whenever you have a method using Vec2 with the in modifier, you can safely call the above without a copy being made.

(This feature was introduced in C# 8.0 and not available when I asked the question.)

Mutability in fields for structs in Rust

In your example, p and q are indeed immutable, but then you move them into a Line instance with the constructor, since they're passed by value and don't implement Copy to enable implicit copying. This means that the original bindings (p and q) are no longer valid (the compiler will prevent you from using them) and the values are instead accessible only via the mutable line binding, which allows mutating its members. Essentially, it's not the values that are mutable, but rather their bindings. For example, the following code can be used to re-bind a value to change its mutability:

let x = String::from("hello world"); // using String as an example of a non-Copy type

let mut x = x; // this shadows the previous binding with a mutable one of the same name
x.make_ascii_uppercase(); // we can now mutate the string
let x = x; // shadow the mutable binding with an immutable one

println!("Result: {}", x);

This example works because we have direct control over the value, and can move/bind it as desired. Introducing references would limit what could be done - for example, the following examples wouldn't work:

let x = String::from("hello world");
let x_ref = &x; // create an immutable reference to x
let mut x_mut = x; // error - we can't move x while it's borrowed
let x_mut_ref = &mut x; // error - we can't create a mutable reference while any other references exist

I'd recommend reading the ownership and moves page of Rust by example, which explains it pretty well.



Related Topics



Leave a reply



Submit