Why are mutable structs “evil”?
Asked Answered
P

16

555

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#?

Photoemission answered 13/1, 2009 at 23:27 Comment(12)
Claiming mutable structs are evil is like claiming mutable ints, bools, and all other value types are evil. There are cases for mutability and for immutability. Those cases hinge on the role the data plays, not the type of memory allocation/sharing.Encounter
@slipp int and bool are not mutable..Botvinnik
.-syntax, making operations with ref-typed data and value-typed data look the same even though they're distinctly different. This is a fault of C#'s properties, not structs— some languages offer an alternate a[V][X] = 3.14 syntax for mutating in-place. In C#, you'd do better to offer struct-member mutator methods like ’MutateV(Action<ref Vector2> mutator)` and use it like a.MutateV((v) => { v.X = 3; }) (example is over-simplified because of the limitations C# has regarding the ref keyword, but with some workarounds should be possible).Encounter
@Sushi271 workaround (n): A method for overcoming a problem or limitation in a program or system. This is a limitation in C#'s syntax. The ideal usage of the ref keyword in a generic type isn't ambiguous; it's just not supported. That's a limitation. And yes, C# in many ways forces inconvenient syntax to get the job done. The most classic example of C#'s clumsiness is public over and over and over on every member rather than a C++-style public: prefix or a Ruby-style separate public FieldName1, FieldName2, MethodName1, MethodName2;Encounter
@Sushi271 Structs with many members aren't common because of the inefficiency of copying a lot of data members around. The use-cases are the same, but good programmers will (and have, historically) choose a different approach for the sake of efficiency. So, yes, lightweight is usually better for any value-typed data.Encounter
@Slipp Well, I think exactly opposite about these kind of structs. Why do you think that structs that are already implemented in .NET library, like DateTime or TimeSpan (so similar ones) are immutable? Maybe it could be useful to change only one member of such struct's var, but it's just too inconvenient, leads to too many problems. Actually you are wrong about what does the processor calc, since C# does not compile to assembler, it compiles to IL. In IL (providing we already have the variable named x) this single operation is 4 instructions: ldloc.0 (loads the 0-index variable into...Deerhound
... type. T is type. Ref is just a keyword that makes variable being passed to a method itself, not a copy of it. It also has sense for the reference types, since we can change the variable, i.e. the reference outside the method will point to other object after being changed within the method. Since ref T is not a type, but fashion of passing a method parameter, you cannot put it into <>, cause only types can be put there. So it's just incorrect. Maybe it would be convenient to do so, maybe the C# team could make this for some new version, but right now they're working on some...Deerhound
... much more crucial things like null-conditional operator or auto-property initializers. As for the public... I actually like the C# way of doing this. It's actually easier to refactor, cause deleting/adding one public in one place for one member does not affect all other members. In C++ I often have to add two labels (e.g. public: and private: again), or move the method to other part of class. Inconvenient. Anyway, mentioning this topic made me realise that we should end that discussion. We are starting to argue about our own opinions, and we could battle for years and not find a common..Deerhound
ground. Everyone has right for his own opinion. You, sir, seem to be more bound to C++, whereas my primary language for the last 8 years has been C#. It's certainly educating to learn someone's point of view on some matters, however right now we strayed far off the main topic. So thanks for your input.Deerhound
Here we are in 2017 and mutable structs are back! :D Just as it has always been it's about knowledge of how and when to use them. That said I would probably say to any beginner "Stay away from structs or at least make them mutable - then come back to them later when you have more experience, they offer special benefits". (Understanding that a reference is a special address value-type with special operator overloads solves everything, but that is just too much to chew for beginners.)Cristen
I agree with @Cristen mutable structs are only "evil" if you don't know the difference between value and reference types, otherwise it's valid to use structs, even if the are mutable, as long as you know what you are doing. (btw .NET itself has many mutable structs)Ondine
This question should've been locked years ago, being primarily opinion-based. Whether or not you choose to ignore valid use cases of mutable structs is entirely your choice as a programmer. But debating about whether or not something that does exist should exist serves no purpose here on SO specifically considering the plethora of resources that exist to allow anyone to make that distinction on their own.Urbai
T
327

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)

Tenotomy answered 13/1, 2009 at 23:42 Comment(9)
"If your struct is immutable then all copies will be the same." No, it means that you have to consciously make a copy if you want a different value. It means you won't get caught modifying a copy thinking you are modifying the original.V2
@V2 I think you are talking about a different kind of copy I am talking about the automatic copies made as a result of being passed by value, Your 'consciously made copy' is different on purpose you didn't make it by mistake and its not really a copy its a deliberate new instants containing different data.Tenotomy
Your edit (16 months later) makes that a little clearer. I still stand by "(immutable struct) means you won't get caught modifying a copy thinking you are modifying the original", though.V2
@Lucas: The danger of making a copy of a struct, modifying it, and somehow thinking one is modifying the original (when the fact that one is writing a struct field makes self-apparent the fact that one is only writing one's copy) seems pretty small compared to the danger that someone who holds a class object as a means of holding the information contained therein will mutate the object to update its own information and in the process corrupt the information held by some other object.Willardwillcox
So to avoid the "evil", do we need to make class wrappers for every struct(Rectangle, Size, Point, etc) we encounter?? Isn't that unwieldy?Nicker
@VictorPrograss: Store a struct in a single-element array and it will work like a class. Alternatively, if one defines a class public ExposedFieldHolder<T> { public T Value; public ExposedFieldHolder(T v) { Value = v; } } then one could use it to turn any structure into an entity (making the type public is harmless, since the whole purpose of the type is to have exactly the indicated semantics, and exposing it would not expose any instances to code that shouldn't see them).Willardwillcox
For me, the biggest advantage of immutable data structures is that since they can't be modified, you can easily make multi-threaded distributed work on a piece of data without having to manually check and handle synchronism problems (cross thread access to that data). This essentially makes the code cleaner, more robust and bug free than if you use a mutable data structure (you essentially force, by design, that any threads looking at it can't modify it). There is still the problem of sequentiality. If 2 threads look at the same place for data and the pointer to that"copy" has changed since.Dutch
I don't think there is any convention saying that structs should be immutable. You can find plenty of mutable structs in System.Drawing for example (Point, Rectangle, ...).Incursion
The 3rd paragraph sounds wrong or unclear at best. If your struct is immutable then you simply won't be able to modify its fields or the fields of any copies made. "If you want to change it you have to..." that's misleading too, you can't change it ever, neither consciously nor unconsciously. Creating a new instance which the data you want has nothing to do with the original copy other than having the same data structure.Duologue
U
182

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).

Untutored answered 13/1, 2009 at 23:31 Comment(15)
Well yes but the compiler is just stupid that way. Not to allow assignment to property-struct members was IMHO a stupid design decision, because it is allowed for ++ operator. In this case, the compiler just writes the explicit assignment itself instead of hustling the programmer.Cymene
@Konrad: myObj.SomeProperty.Size = 22 would modify a COPY of myObj.SomeProperty. The compiler is saving you from an obvious bug. And it is NOT allowed for ++.V2
@Lucas: Well, the Mono C# compiler certainly allows it – since I don’t have Windows I can’t check for Microsoft’s compiler.Cymene
@Konrad - MS compiler says no to: ` class Test { public MyStruct Value { get; set; } public struct MyStruct { public int Bar { get; set; } } public static void Main() { Test test = new Test(); test.Value.Bar++; } }` - does that work on mono?Untutored
@Marc: I was actually talking about one less indirection: test.Bar++ or test.Bar += 1. This is also mutating the value – in fact, it’s more or less equivalent to var tmp = test.Bar; tmp += 1; test.Bar = tmp;. So the situation is not completely identical but that was never my point. My point was that the compiler can and does rewrite code where it makes sense. And the resulting rewrite here is almost completely identical to the manual rewrite you proposed in your answer. The only thing that’s different is bar.Size = 22; vs. bar = bar + 1.Cymene
@Marc: Duh. I take that back – there’s a crucial difference here, my code is always creating a new object, it also works with immutables.Cymene
@Konrad - with one less indirection it should work; it is the "mutating a value of something that only exists as a transient value on the stack and which is about to evaporate into nothingness" which is the case that is blocked.Untutored
@Marc Gravell: In the former piece of code, you end up with a "Foo" whose name is "abc" and whose other attributes are those of List[0], without disturbing List[0]. If Foo were a class, it would be necessary to clone it and then change the copy. To my mind, the big problem with the value-type vs class distinction is the use of the "." operator for two purposes. If I had my druthers, classes could support both "." and "->" for methods and properties, but the normal semantics for "." properties would be to create a new instance with the appropriate field modified.Willardwillcox
Why should myObj.SomeProperty.Size = modify a copy of myObj.SomeProperty but bar.Size = modify bar and not a copy of bar? That is where the poor decision seems to be...Blowhole
I tried myObj.SomeProperty.Size = 22; and it compiles and runs fine.Gentlemanfarmer
@Backwards_Dave you might be comparing a different scenario, then; either SomeProperty is not actually a property (perhaps it is a field?), or the type of SomeProperty is not actually a struct. Here's a minimal repro that shows CS1612: sharplab.io/…Untutored
@MarcGravell you're right. Thanks for clearing it up. Once you realise a property is just a method which returns a value, then it's obvious that the value returned is a copy of the value type!Gentlemanfarmer
"..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" makes it sound like a given negative. However, given the extra overhead for "heap" objects and references to such, that an un-burn-through-memory only applies if the 'same object' is 'referenced' multiple/many times and cached/reused correctly.Rhett
Maybe something's been lost after edits, but the inclusion of Eric's quote adds zero value in answering the OP's question. I think we should just remove of the quote and say "Eric Lippert's blog covers this extensively". Then again you are a very smart guy and I am not, so I may be missing something.Specialty
@Specialty the quote was added about 5 minutes after the original answer, so in the grand scheme of 11+ years, we should probably consider it part of the original answer itself. Ultimately, the quote was simply a pithy headline to set the scene for Eric's blog, and while "Eric's blog covers this extensively" would also have achieved that, we must allow some room in life for a little artistic flourish now and again :)Untutored
C
82

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.

Cymene answered 13/1, 2009 at 23:34 Comment(11)
Eric Lippert says they are... see my answer.Untutored
Immutability is certainly good with threading, but you can write an immutable class just as easily and just as usefully. But still a good answer. I would +1, but I'm out for today.Untutored
Much as I respect Eric Lippert he isn't God (or at least not yet). The blog post you link to and your post above are reasonable arguments for making structs immutable as matter of course but they are actually very weak as arguments for never using mutable structs. This post, however, is a +1.Annals
Developing in C#, you usually need mutability ever now and then - especially with your Business Model, where you want streaming etc. to work smoothly with existing solutions. I wrote an article on how to work with mutable AND immutable data, solving most issues around mutability (I hope): rickyhelgesson.wordpress.com/2012/07/17/…Miner
@RickyHelgesson: One thing I think would be useful as part of a design that includes mutable and immutable business objects is a common "readable object" interface. Methods which neither mutate nor persist their argument should be able to operate interchangeably with mutable or immutable objects.Willardwillcox
@StephenMartin: Structs which encapsulate a single value often should be immutable, but structs are by far the best medium for encapsulating fixed sets of independent but related variables (like the X and Y coordinates of a point) which have no "identity" as a group. Structs which are used for that purpose should generally expose their variables as public fields. I would consider the notion that it's more appropriate to use a class than a struct for such purposes to be just plain wrong. Immutable classes are often less efficient, and mutable classes often have dreadful semantics.Willardwillcox
@StephenMartin: Consider, for example, a method or property that is supposed to returns the six float components of a graphics transform. If such a method returns an exposed-field struct with six components, it's obvious that modifying the fields of the struct won't modify the graphics object from which it was received. If such a method returns a mutable class object, maybe changing its properties will change the underlying graphics object and maybe it won't--nobody really knows.Willardwillcox
@supercat: I cannot see what problem that would solve. The importance of the immutable access of an object is that you know that it will be consistent between reads and all properties of the object are obviously consistent regards to each other all the time. A readable interface would allow you to read data from an object that might be instable and possibly even dangerous to read if you have properties that cannot be updated in a single operation, making the interface dangerous. So, if I would add that, it would have to be a very important problem to solve, to introduce these dangers.Miner
@RickyHelgesson: Code which needs an immutable object should demand a mutable object. The "readable" interface would be for code which wants to read the present state of an object which could be mutable. Among other things, if the mutable type constructor would be willing to accept a mutable object, there would be in many cases no reason it shouldn't also be perfectly happy if it was given an object which held the required information but was immutable. Defining the "readable" interface would avoid the need to overload the mutable-object constructor for mutable or immutable source operands.Willardwillcox
Why not mutable? I have Rectangle property which could be my screen size, and I would like to change it outside of the class.Artemisa
@Artemisa I have given reasons for this in my post, and the one linked.Cymene
M
67

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".

Mcshane answered 4/6, 2013 at 8:10 Comment(1)
I would also posit that if one wants to fasten a fixed set of variables together with duct tape so their values can be processed or stored either separately or as a unit, it makes a lot more sense to ask the compiler to fasten a fixed set of variables together (i.e. declare a struct with public fields) than to define a class which can be used, clumsily, to achieve the same ends, or to add a bunch of junk to a struct to make it emulate such a class (rather than having it behave like a set of variables stuck together with duct tape, which is what one really wants in the first place)Willardwillcox
W
50

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.

Willardwillcox answered 29/9, 2011 at 3:28 Comment(11)
@Ron Warholic: it's not self-apparent that SomeRect is a Rectangle. It could be some other type which can be implicitly typecast from Rectangle. Although, the only system-defined type which can be implicitly typecast from Rectangle is RectangleF, and the compiler would squawk if one tried to pass the fields of a RectangleF to the constructor of Rectangle (since the former are Single, and the latter Integer), there could be user-defined structs which allow such implicit typecasts. BTW, the first statement would work equally well whether SomeRect were a Rectangle or a RectangleF.Willardwillcox
All you've shown is that in a contrived example you believe one method is clearer. If we take your example with Rectangle I could easily come up with a common sitation where you get highly unclear behaviour. Consider that WinForms implements a mutable Rectangle type used in the form's Bounds property. If I want to change bounds I would want to use your nice syntax: form.Bounds.X = 10; However this changes precisely nothing on the form (and generates a lovely error informing you of such). Inconsistency is the bane of programming and is why immutability is wanted.Inseparable
To your second point, I fail to see how that is relevant. Being able to replace a Rectangle with a RectangleF with slightly fewer changes is hardly a common situation and moreso still requires changing the declared type at any declaration sites. I think it's a stretch to claim that being able to slightly modify a type easier is worth the loss of clarity clearly shown in the answers here.Inseparable
@Ron Warholic: The fact that code like "form.Bounds.X = 10" won't compile makes it pretty obvious it won't change anything. My point with RectangleF was simply that the amount of information needed to know the effect of the second statement is much greater than that for the first. If you saw the code "MyRect = New Rectangle(MyRect.Left+10, MyRect.Right, MyRect.Width, MyRect.Height);" can you say for certain that you'd correctly identify what it does? Also, suppose Rectangle were a class. What would you expect "someForm.Bounds.X=10;" to do. Why?Willardwillcox
@Ron Warholic: BTW, I would like to be able to say "form.Bounds.X = 10;" and have it just work, but the system doesn't provide any clean way of doing so. A convention for exposing value-type properties as methods accepting call-backs could offer much cleaner, efficient, and confirmably-correct code than any approach using classes.Willardwillcox
let us continue this discussion in chatInseparable
This answer is so much more insightful than a few of the top-voted answers. It's sort of absurd that the argument against mutable value types relies on the notion of "what you expect" to have happen when you mix aliasing and mutation. That's a terrible thing to do anyhow!Vince
@EamonNerbonne: I hadn't thought of that phraseology, but you're exactly right, and it ties in with why exposed-field structures are often the right approach: they support only ephemeral aliases. It's too bad .NET languages don't have a good pattern for exposing ephemeral aliases, though; I wish there were a pattern such that "foo.Bar(57).X=boz;" could auto-translate as "foo.access_Bar(57, (ref Point it, ref int p1) => it.X=p1, ref boz)", thus allowing collections other than arrays to allow access to the internals of structs held therein.Willardwillcox
@supercat: Who knows, maybe that ref-return feature they're talking about for C# 7 might cover that base (I haven't actually looked at it in detail, but it superficially sounds similar).Vince
@EamonNerbonne: The intent may be similar, but IMHO a proper access protocol should let the collection know when an object is being accessed, and when the recipient is done with it. From an implementation standpoint, a "ref-return" followed by a try-finally-guarded second method call would be a good approach, but I don't think there are any plans for that.Willardwillcox
@Willardwillcox see if you want to update this answer with 7.2: blogs.msdn.microsoft.com/seteplia/2018/03/07/…Chipper
E
27

There are a couple other corner cases that could lead to unpredictable behavior from the programmer's point of view.

Immutable value types and readonly fields

    // Simple mutable structure. 
    // Method IncrementI mutates current state.
    struct Mutable
    {
        public Mutable(int i) : this() 
        {
            I = i;
        }

        public void IncrementI() { I++; }

        public int I { get; private set; }
    }

    // Simple class that contains Mutable structure
    // as readonly field
    class SomeClass 
    {
        public readonly Mutable mutable = new Mutable(5);
    }

    // Simple class that contains Mutable structure
    // as ordinary (non-readonly) field
    class AnotherClass 
    {
        public Mutable mutable = new Mutable(5);
    }

    class Program
    {
        void Main()
        {
            // Case 1. Mutable readonly field
            var someClass = new SomeClass();
            someClass.mutable.IncrementI();
            // still 5, not 6, because SomeClass.mutable field is readonly
            // and compiler creates temporary copy every time when you trying to
            // access this field
            Console.WriteLine(someClass.mutable.I);

            // Case 2. Mutable ordinary field
            var anotherClass = new AnotherClass();
            anotherClass.mutable.IncrementI();

            // Prints 6, because AnotherClass.mutable field is not readonly
            Console.WriteLine(anotherClass.mutable.I);
        }
    }

Mutable value types and array

Suppose we have an array of our Mutable struct and we're calling the IncrementI method for the first element of that array. What behavior are you expecting from this call? Should it change the array's value or only a copy?

    Mutable[] arrayOfMutables = new Mutable[1];
    arrayOfMutables[0] = new Mutable(5);

    // Now we actually accessing reference to the first element
    // without making any additional copy
    arrayOfMutables[0].IncrementI();

    // Prints 6!!
    Console.WriteLine(arrayOfMutables[0].I);

    // Every array implements IList<T> interface
    IList<Mutable> listOfMutables = arrayOfMutables;

    // But accessing values through this interface lead
    // to different behavior: IList indexer returns a copy
    // instead of an managed reference
    listOfMutables[0].IncrementI(); // Should change I to 7

    // Nope! we still have 6, because previous line of code
    // mutate a copy instead of a list value
    Console.WriteLine(listOfMutables[0].I);

So, mutable structs are not evil as long as you and the rest of the team clearly understand what you are doing. But there are too many corner cases when the program behavior would be different from what's expected, that could lead to subtle hard to produce and hard to understand errors.

Engrossment answered 14/7, 2011 at 10:26 Comment(6)
What should happen, if .net languages had slightly better value-type support, would be struct methods should be forbidden from mutating 'this' unless they are explicitly declared as doing so, and methods that are so declared should be forbidden in read-only contexts. Arrays of mutable structs offer useful semantics which cannot be efficiently achieved via other means.Willardwillcox
these are good examples of very subtle issue that would arise from mutable structs. I would not have expected any of this behaviour. Why would an array give you a reference, but an interface give you a value? I would have thought, aside from values-all-the-time (which is what I'd really expect), that it would at least be the other way around: interface giving references; arrays giving values...Niles
@Sahuagin: Unfortunately, there is no standard mechanism by which an interface can expose a reference. There are ways .net could allow such things to be done safely and usefully (e.g. by defining a special "ArrayRef<T>" struct containing a T[] and an integer index, and providing that an access to a property of type ArrayRef<T> will be interpreted as an access to the appropriate array element) [if a class wanted to expose an ArrayRef<T> for any other purpose, it could provide a method--as opposed to a property--to retrieve it]. Unfortunately, no such provisions exist.Willardwillcox
Oh my... this makes mutable structs damn evil!Tipperary
When you refactor the mutating methods into static methods requiring a ref parameter: public static void IncrementI(ref Mutable m) { m.I++; } then the compiler should stop you from doing the "wrong" things most at the time.Kinslow
I like this answer because it contains very valuable information that is non obvious. But really though, this is not an argument against mutable structs as some claim. Yes what we see here is a "pit of despair" as Eric would have put it, but the source of this despair isn't mutability. The source of despair is the structs self-mutating methods. (As for why arrays and lists behave differently it's because one is basically an operator that calculates a memory address and the other is a property. In general it all becomes clear once you understanding that a "reference" is an address value.)Cristen
I
23

Value types basically represents immutable concepts. Fx, it makes no sense to have a mathematical value such as an integer, vector etc. and then be able to modify it. That would be like redefining the meaning of a value. Instead of changing a value type, it makes more sense to assign another unique value. Think about the fact that value types are compared by comparing all the values of its properties. The point is that if the properties are the same then it is the same universal representation of that value.

As Konrad mentions it doesn't make sense to change a date either, as the value represents that unique point in time and not an instance of a time object which has any state or context-dependency.

Hopes this makes any sense to you. It is more about the concept you try to capture with value types than practical details, to be sure.

Idaho answered 13/1, 2009 at 23:52 Comment(9)
Well, they should represent immutable concepts, at least ;-pUntutored
True, but I suppose you can misuse most programming constructsIdaho
Well, I suppose they could have made System.Drawing.Point immutable but it would have been a serious design error IMHO. I think points are actually an archetypical value type and they are mutable. And they don't cause any problems for anyone beyond really early programming 101 beginners.Annals
In principle I think points should also be immutable but if it makes the type harder or less elegant to use then of course that has to be considered too. There's no point in having code constructs which uphold the finest princicples if no one wants to use them ;)Idaho
Value-types are useful for representing simple immutable concepts, but exposed-field structures are the best types to use to hold or pass around small fixed sets of related but independent values (such as the coordinates of a point). A storage location of such a value type encapsulates the values of its fields and nothing else. By contrast, a storage location of a mutable reference type may be used for the purpose of holding the state of the mutable object, but also encapsulates the identity of all other references throughout the universe that exist to that same object.Willardwillcox
@Willardwillcox That is the best opinion. A structure is just a bunch of values. When you start to see it that way, no corner case should catch you.Fiducial
@IllidanS4: Exactly. Structs can be useful for things other than simple aggregations, and structs that pretend to be low-cost objects should behave like objects (generally implying they must be immutable), but for structs which are used simply as aggregations, the more open the struct, the more readily someone will be able to see that it is nothing more than an aggregation.Willardwillcox
“Value types basically represents immutable concepts”. No, they don't. One of the oldest and most useful applications of a value-typed variable is an int iterator, which would be completely useless if it were immutable. I think you're conflating “value types' compiler/runtime implementations” with “variables typed to a value type”— the latter is certainly mutable to any of the possible values.Encounter
By the logic you've stated in this answer, all types are immutable. Classes are stored as collections of value types and references (memory address pointers/handles) — therefore they're also immutable since you don't change the memory address, you just “assign another unique value”. The Q is clearly about the proposed usage of struct-category data structures in a way that changes what values and memory locations they contain at a time after initialization, from a high-level programmers' perspective. Switching the discussion to compiler optimizations makes this A irrelevant.Encounter
C
19

If you have ever programmed in a language like C/C++, structs are fine to use as mutable. Just pass them with ref, around and there is nothing that can go wrong. The only problem I find are the restrictions of the C# compiler and that, in some cases, I am unable to force the stupid thing to use a reference to the struct, instead of a Copy(like when a struct is part of a C# class).

So, mutable structs are not evil, C# has made them evil. I use mutable structs in C++ all the time and they are very convenient and intuitive. In contrast, C# has made me to completely abandon structs as members of classes because of the way they handle objects. Their convenience has cost us ours.

Costanza answered 18/10, 2013 at 11:22 Comment(1)
Having class fields of structure types can often be a very useful pattern, though admittedly there are some limitations. Performance will be degraded if one uses properties rather than fields, or uses readonly, but if one avoids doing those things class fields of structure types are just fine. The only really fundamental limitation of structures is that a struct field of a mutable class type like int[] may encapsulate identity or an unchanging set of values, but cannot be used to encapsulate mutable values without also encapsulating an unwanted identity.Willardwillcox
U
14

Imagine you have an array of 1,000,000 structs. Each struct representing an equity with stuff like bid_price, offer_price (perhaps decimals) and so on, this is created by C#/VB.

Imagine that array is created in a block of memory allocated in the unmanaged heap so that some other native code thread is able to concurrently access the array (perhaps some high-perf code doing math).

Imagine the C#/VB code is listening to a market feed of price changes, that code may have to access some element of the array (for whichever security) and then modify some price field(s).

Imagine this is being done tens or even hundreds of thousands of times per second.

Well lets face facts, in this case we really do want these structs to be mutable, they need to be because they are being shared by some other native code so creating copies isn't gonna help; they need to be because making a copy of some 120 byte struct at these rates is lunacy, especially when an update may actually impact just a byte or two.

Hugo

Uranus answered 21/3, 2010 at 22:37 Comment(2)
True, but in this case the reason for using a struct is that doing so is imposed upon the application design by outside constraints (those by the native code's use). Everything else you describe about these objects suggests they should clearly be classes in C# or VB.NET.Nicolella
I'm not sure why some people think the things should be class objects. If all array slots are populated with references distinct instances, using a class type will add an extra twelve or twenty-four bytes to the memory requirement, and sequential access on an array of class object references is apt to be much slower than sequential access on an array of structs.Willardwillcox
T
14

If you stick to what structs are intended for (in C#, Visual Basic 6, Pascal/Delphi, C++ struct type (or classes) when they are not used as pointers), you will find that a structure is not more than a compound variable. This means: you will treat them as a packed set of variables, under a common name (a record variable you reference members from).

I know that would confuse a lot of people deeply used to OOP, but that's not enough reason to say such things are inherently evil, if used correctly. Some structures are inmutable as they intend (this is the case of Python's namedtuple), but it is another paradigm to consider.

Yes: structs involve a lot of memory, but it will not be precisely more memory by doing:

point.x = point.x + 1

compared to:

point = Point(point.x + 1, point.y)

The memory consumption will be at least the same, or even more in the inmutable case (although that case would be temporary, for the current stack, depending on the language).

But, finally, structures are structures, not objects. In POO, the main property of an object is their identity, which most of the times is not more than its memory address. Struct stands for data structure (not a proper object, and so they don't have identity anyhow), and data can be modified. In other languages, record (instead of struct, as is the case for Pascal) is the word and holds the same purpose: just a data record variable, intended to be read from files, modified, and dumped into files (that is the main use and, in many languages, you can even define data alignment in the record, while that's not necessarily the case for properly called Objects).

Want a good example? Structs are used to read files easily. Python has this library because, since it is object-oriented and has no support for structs, it had to implement it in another way, which is somewhat ugly. Languages implementing structs have that feature... built-in. Try reading a bitmap header with an appropriate struct in languages like Pascal or C. It will be easy (if the struct is properly built and aligned; in Pascal you would not use a record-based access but functions to read arbitrary binary data). So, for files and direct (local) memory access, structs are better than objects. As for today, we're used to JSON and XML, and so we forget the use of binary files (and as a side effect, the use of structs). But yes: they exist, and have a purpose.

They are not evil. Just use them for the right purpose.

If you think in terms of hammers, you will want to treat screws as nails, to find screws are harder to plunge in the wall, and it will be screws' fault, and they will be the evil ones.

Tonometer answered 2/12, 2015 at 15:29 Comment(0)
B
10

When something can be mutated, it gains a sense of identity.

struct Person {
    public string name; // mutable
    public Point position = new Point(0, 0); // mutable

    public Person(string name, Point position) { ... }
}

Person eric = new Person("Eric Lippert", new Point(4, 2));

Because Person is mutable, it's more natural to think about changing Eric's position than cloning Eric, moving the clone, and destroying the original. Both operations would succeed in changing the contents of eric.position, but one is more intuitive than the other. Likewise, it's more intuitive to pass Eric around (as a reference) for methods to modify him. Giving a method a clone of Eric is almost always going to be surprising. Anyone wanting to mutate Person must remember to ask for a reference to Person or they'll be doing the wrong thing.

If you make the type immutable, the problem goes away; if I can't modify eric, it makes no difference to me whether I receive eric or a clone of eric. More generally, a type is safe to pass by value if all of its observable state is held in members that are either:

  • immutable
  • reference types
  • safe to pass by value

If those conditions are met then a mutable value type behaves like a reference type because a shallow copy will still allow the receiver to modify the original data.

The intuitiveness of an immutable Person depends on what you're trying to do though. If Person just represents a set of data about a person, there's nothing unintuitive about it; Person variables truly represent abstract values, not objects. (In that case, it'd probably be more appropriate to rename it to PersonData.) If Person is actually modeling a person itself, the idea of constantly creating and moving clones is silly even if you've avoided the pitfall of thinking you're modifying the original. In that case it'd probably be more natural to simply make Person a reference type (that is, a class.)

Granted, as functional programming has taught us there are benefits to making everything immutable (no one can secretly hold on to a reference to eric and mutate him), but since that's not idiomatic in OOP it's still going to be unintuitive to anyone else working with your code.

Burgoo answered 2/10, 2013 at 13:52 Comment(3)
Your point about identity is a good one; it may be worth noting that identity is relevant only when multiple references exists to something. If foo holds the only reference to its target anywhere in the universe, and nothing has captured that object's identity-hash value, then mutating field foo.X is semantically equivalent to making foo point to a new object which is just like the one it previously referred to, but with X holding the desired value. With class types, it's generally hard to know whether multiple references exist to something, but with structs it's easy: they don't.Willardwillcox
If Thing is a mutable class type, a Thing[] will encapsulate object identities--whether one wants it to or not--unless one can ensure that no Thing in the array to which any outside references exist will ever be mutated. If one doesn't want the array elements to encapsulate identity, one must generally ensure either that no items to which it holds references will ever be mutated, or that no outside references will ever exist to any items it holds [hybrid approaches can also work]. Neither approach is terribly convenient. If Thing is a structure, a Thing[] encapsulates values only.Willardwillcox
For objects, their identity comes from their location. Reference types' instances have their identity thanks to their location in the memory and you only pass around their identity (a reference), not their data, while value types have their identity in the outer place where they are stored. The identity of your Eric value type comes only from the variable where he is stored. If you pass him around, he'll lose his identity.Fiducial
A
6

It doesn’t have anything to do with structs (and not with C#, either) but in Java you might get problems with mutable objects when they are e.g. keys in a hash map. If you change them after adding them to a map and it changes its hash code, evil things might happen.

Angulation answered 13/1, 2009 at 23:34 Comment(1)
That is true if you use a class as the key in a map, too.Untutored
L
6

There are many advantages and disadvantages to mutable data. The million-dollar disadvantage is aliasing. If the same value is being used in multiple places, and one of them changes it, then it will appear to have magically changed to the other places that are using it. This is related to, but not identical with, race conditions.

The million-dollar advantage is modularity, sometimes. Mutable state can allow you to hide changing information from code that doesn't need to know about it.

The Art of the Interpreter goes into these trade offs in some detail, and gives some examples.

Laquitalar answered 13/1, 2009 at 23:45 Comment(2)
structs are not be aliased in c#. Every struct assignment is a copy.Interpose
@recursive: In some cases, that's a major advantage of mutable structs, and one which makes me question the notion that structs should not be mutable. The fact that compilers sometimes implicitly copy structs doesn't reduce the usefulness of mutable structs.Willardwillcox
A
6

Personally when I look at code the following looks pretty clunky to me:

data.value.set ( data.value.get () + 1 ) ;

rather than simply

data.value++ ; or data.value = data.value + 1 ;

Data encapsulation is useful when passing a class around and you want to ensure the value is modified in a controlled fashion. However when you have public set and get functions that do little more than set the value to what ever is passed in, how is this an improvement over simply passing a public data structure around?

When I create a private structure inside a class, I created that structure to organize a set of variables into one group. I want to be able to modify that structure within the class scope, not get copies of that structure and create new instances.

To me this prevents a valid use of structures being used to organize public variables, if I wanted access control I'd use a class.

Acerbic answered 7/2, 2011 at 17:31 Comment(3)
Straight to the point! Structures are organization units without access control restrictions! Unfortunately, C# has made them useless for this purpose!Costanza
this completely misses the point as both your examples show mutable structs.Supercargo
C# made them useless for this purpose because that's not the purpose of the structuresEarlie
C
6

There are several issues with Mr. Eric Lippert's example. It is contrived to illustrate the point that structs are copied and how that could be a problem if you are not careful. Looking at the example I see it as a result of a bad programming habit and not really a problem with either struct or the class.

  1. A struct is supposed to have only public members and should not require any encapsulation. If it does then it really should be a type/class. You really do not need two constructs to say the same thing.

  2. If you have class enclosing a struct, you would call a method in the class to mutate the member struct. This is what I would do as a good programming habit.

A proper implementation would be as follows.

struct Mutable {
public int x;
}

class Test {
    private Mutable m = new Mutable();
    public int mutate()
    { 
        m.x = m.x + 1;
        return m.x;
    }
  }
  static void Main(string[] args) {
        Test t = new Test();
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
    }

It looks like it is an issue with programming habit as opposed to an issue with struct itself. Structs are supposed to be mutable, that is the idea and intent.

The result of the changes voila behaves as expected:

1 2 3 Press any key to continue . . .

Chiffon answered 12/5, 2015 at 18:2 Comment(1)
There is nothing wrong with designing small opaque structures to behave like immutable class objects; the MSDN guidelines are reasonable when one is trying to make something that behaves like an object. Structures are appropriate in some cases where one needs lightweight things that behave like objects, and in cases where one needs a bunch of variables stuck together with duct tape. For some reason, however, many people fail to realize that structures have two distinct usages, and that guidelines appropriate for one are inappropriate for the other.Willardwillcox
R
1

I don't believe they're evil if used correctly. I wouldn't put it in my production code, but I would for something like structured unit testing mocks, where the lifespan of a struct is relatively small.

Using the Eric example, perhaps you want to create a second instance of that Eric, but make adjustments, as that's the nature of your test (ie duplication, then modifying). It doesn't matter what happens with the first instance of Eric if we're just using Eric2 for the remainder of the test script, unless you're planning on using him as a test comparison.

This would be mostly useful for testing or modifying legacy code that shallow defines a particular object (the point of structs), but by having an immutable struct, this prevents it's usage annoyingly.

Rennin answered 19/12, 2013 at 12:25 Comment(2)
As I see it, a struct is at its heart a bunch of variables stuck together with duct tape. It's possible in .NET for a struct to pretend to be something other than a bunch of variables stuck together with duct tape, and I would suggest that when practical a type which is going to pretend to be something other than a bunch of variables stuck together with duct tape should behave as a unified object (which for a struct would imply immutability), but sometimes it is useful to stick a bunch of variables together with duct tape. Even in production code, I would consider it better to have a type...Willardwillcox
...which clearly has no semantics beyond "each field contains the last thing written to it", pushing all semantics into the code which uses the structure, than to try to have a struct do more. Given, for example, a Range<T> type with members Minimum and Maximum fields of type T, and code Range<double> myRange = foo.getRange();, any guarantees about what Minimum and Maximum contain should come from foo.GetRange();. Having Range be an exposed-field struct would make clear that it's not going to add any behavior of its own.Willardwillcox

© 2022 - 2024 — McMap. All rights reserved.