Why are mutable structs “evil”?

91,529

Solution 1

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)

Solution 2

Where to start ;-p

Eric Lippert's blog is always good for a quote:

This is yet another reason why mutable value types are evil. Try to always make value types immutable.

First, you tend to lose changes quite easily... for example, getting things out of a list:

Foo foo = list[0];
foo.Name = "abc";

what did that change? Nothing useful...

The same with properties:

myObj.SomeProperty.Size = 22; // the compiler spots this one

forcing you to do:

Bar bar = myObj.SomeProperty;
bar.Size = 22;
myObj.SomeProperty = bar;

less critically, there is a size issue; mutable objects tend to have multiple properties; yet if you have a struct with two ints, a string, a DateTime and a bool, you can very quickly burn through a lot of memory. With a class, multiple callers can share a reference to the same instance (references are small).

Solution 3

I wouldn't say evil but mutability is often a sign of overeagerness on the part of the programmer to provide a maximum of functionality. In reality, this is often not needed and that, in turn, makes the interface smaller, easier to use and harder to use wrong (= more robust).

One example of this is read/write and write/write conflicts in race conditions. These simply can't occur in immutable structures, since a write is not a valid operation.

Also, I claim that mutability is almost never actually needed, the programmer just thinks that it might be in the future. For example, it simply doesn't make sense to change a date. Rather, create a new date based off the old one. This is a cheap operation, so performance is not a consideration.

Solution 4

Mutable structs are not evil.

They are absolutely necessary in high performance circumstances. For example when cache lines and or garbage collection become a bottleneck.

I would not call the use of a immutable struct in these perfectly valid use-cases "evil".

I can see the point that C#'s syntax does not help to distinguish the access of a member of a value type or of a reference type, so I am all for preferring immutable structs, that enforce immutability, over mutable structs.

However, instead of simply labelling immutable structs as "evil", I would advise to embrace the language and advocate more helpful and constructive rule of thumbs.

For example: "structs are value types, which are copied by default. you need a reference if you don't want to copy them" or "try to work with readonly structs first".

Solution 5

Structs with public mutable fields or properties are not evil.

Struct methods (as distinct from property setters) which mutate "this" are somewhat evil, only because .net doesn't provide a means of distinguishing them from methods which do not. Struct methods that do not mutate "this" should be invokable even on read-only structs without any need for defensive copying. Methods which do mutate "this" should not be invokable at all on read-only structs. Since .net doesn't want to forbid struct methods that don't modify "this" from being invoked on read-only structs, but doesn't want to allow read-only structs to be mutated, it defensively copies structs in read-only contexts, arguably getting the worst of both worlds.

Despite the problems with the handling of self-mutating methods in read-only contexts, however, mutable structs often offer semantics far superior to mutable class types. Consider the following three method signatures:

struct PointyStruct {public int x,y,z;};
class PointyClass {public int x,y,z;};

void Method1(PointyStruct foo);
void Method2(ref PointyStruct foo);
void Method3(PointyClass foo);

For each method, answer the following questions:

  1. Assuming the method doesn't use any "unsafe" code, might it modify foo?
  2. If no outside references to 'foo' exist before the method is called, could an outside reference exist after?

Answers:

Question 1:
Method1(): no (clear intent)
Method2(): yes (clear intent)
Method3(): yes (uncertain intent)
Question 2:
Method1(): no
Method2(): no (unless unsafe)
Method3(): yes

Method1 can't modify foo, and never gets a reference. Method2 gets a short-lived reference to foo, which it can use modify the fields of foo any number of times, in any order, until it returns, but it can't persist that reference. Before Method2 returns, unless it uses unsafe code, any and all copies that might have been made of its 'foo' reference will have disappeared. Method3, unlike Method2, gets a promiscuously-sharable reference to foo, and there's no telling what it might do with it. It might not change foo at all, it might change foo and then return, or it might give a reference to foo to another thread which might mutate it in some arbitrary way at some arbitrary future time. The only way to limit what Method3 might do to a mutable class object passed into it would be to encapsulate the mutable object into a read-only wrapper, which is ugly and cumbersome.

Arrays of structures offer wonderful semantics. Given RectArray[500] of type Rectangle, it's clear and obvious how to e.g. copy element 123 to element 456 and then some time later set the width of element 123 to 555, without disturbing element 456. "RectArray[432] = RectArray[321]; ...; RectArray[123].Width = 555;". Knowing that Rectangle is a struct with an integer field called Width will tell one all one needs to know about the above statements.

Now suppose RectClass was a class with the same fields as Rectangle and one wanted to do the same operations on a RectClassArray[500] of type RectClass. Perhaps the array is supposed to hold 500 pre-initialized immutable references to mutable RectClass objects. in that case, the proper code would be something like "RectClassArray[321].SetBounds(RectClassArray[456]); ...; RectClassArray[321].X = 555;". Perhaps the array is assumed to hold instances that aren't going to change, so the proper code would be more like "RectClassArray[321] = RectClassArray[456]; ...; RectClassArray[321] = New RectClass(RectClassArray[321]); RectClassArray[321].X = 555;" To know what one is supposed to do, one would have to know a lot more both about RectClass (e.g. does it support a copy constructor, a copy-from method, etc.) and the intended usage of the array. Nowhere near as clean as using a struct.

To be sure, there is unfortunately no nice way for any container class other than an array to offer the clean semantics of a struct array. The best one could do, if one wanted a collection to be indexed with e.g. a string, would probably be to offer a generic "ActOnItem" method which would accept a string for the index, a generic parameter, and a delegate which would be passed by reference both the generic parameter and the collection item. That would allow nearly the same semantics as struct arrays, but unless the vb.net and C# people can be pursuaded to offer a nice syntax, the code is going to be clunky-looking even if it is reasonably performance (passing a generic parameter would allow for use of a static delegate and would avoid any need to create any temporary class instances).

Personally, I'm peeved at the hatred Eric Lippert et al. spew regarding mutable value types. They offer much cleaner semantics than the promiscuous reference types that are used all over the place. Despite some of the limitations with .net's support for value types, there are many cases where mutable value types are a better fit than any other kind of entity.

Share:
91,529
Dirk Vollmar
Author by

Dirk Vollmar

Follow me on Twitter @dirvo!

Updated on May 02, 2020

Comments

  • Dirk Vollmar
    Dirk Vollmar about 4 years

    Following the discussions here on SO I already read several times the remark that mutable structs are “evil” (like in the answer to this question).

    What's the actual problem with mutability and structs in C#?