3

I've read When should I use a struct instead of a class? which references MSDN's guidance, which says:

Do not define a structure unless the type has all of the following characteristics:

  • It logically represents a single value, similar to primitive types (integer, double, and so on).
  • It has an instance size smaller than 16 bytes.
  • It is immutable.
  • It will not have to be boxed frequently.

and Why are mutable structs “evil”? has answer:

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

I'm struggling to understand why I shouldn't just be conscious that structs need to be passed by reference, else changes made to the version passed through a function will not affect the original. Yes, I've had tons of bugs from this in the past, but now I'm experienced enough that I'm careful not to have it happen.

I make exclusively games in C#, and variables for stuff in games need to change a lot.

Say I have a player character, represented by class "Player" in my game. If Player has variables X and Y to say where it is, these would change a lot. I wouldn't want to create a new Player every time it moves, just to preserve immutability? So my class should be mutable. OK.

But what if I want to store data about my entity. If I store its position on the screen in a Rectangle (x,y,w,h), should I make Rectangle a CLASS because it's going to be mutable? It's just a logical collection of 4 variables, so a struct seems sensible as a container to me.

I also have "Colour" as a struct (r,g,b,a), but I have it as mutable because I might want to shift the alpha of things in the game to fade them in/out, or colour something red for a second when it gets hurt, without the overhead of calling "new Colour" every render call.

I saw another post which said structs should be used if these conditions are met:

  • Is the main responsibility of the type data storage?
  • Is its public interface defined entirely by properties that access or modify its data members?
  • Are you sure your type will never have subclasses?
  • Are you sure your type will never be treated polymorphically?

The answer to this would be "yes" for the structs I use, but most of them would be mutable.

I'm confused about all of the conflicting advice here. Are mutable structs OK for certain types of use, or am I designing my code all wrong?

Community
  • 1
  • 1
Haighstrom
  • 577
  • 5
  • 16
  • 3
    I find most game engines and game development practices conflict with the "best practices" of other applications for performance reasons. Mutable structs is one such example. – Lukazoid Apr 23 '16 at 11:54
  • 1
    While this isn't limited to just structs, having immutable things that you just project/manipulate makes multi-threading easier. I can imagine there being a situation where that's bad, though. Once you have an object, it won't change under you so you can thread it around. Also consider that business applications are typically assumed to be lots of users doing "little" actions. The "best practices" are centered around standard situations; When you're squeezing out as much performance as you can like you would in a game that is computationally heavy, you sometimes may need to get creative. – Jesus is Lord Apr 23 '16 at 11:57
  • @Lukazoid OK that makes perfect sense as an answer. It does give me a problem, however, which is how can I know which other "best practices" should be ignored in the context of a Game Engine? – Haighstrom Apr 23 '16 at 11:58
  • A major reason why game devs prefer structs over classes is, structs are allocated on the stack instead of the heap, so they won't add pressure to the garbage collector - which is a pain in the ass for all C# game devs. – hillin Apr 23 '16 at 12:40
  • @Haighstrom - "how can I know which other "best practices" should be ignored". Well, first it's "can be", not necessarily "should be". Second, and this might be the frustrating part: experience. *Best* practice implies that there are other ways to do it. Programming is all about tradeoffs, and best practices try to describe ways which *in most contexts* have the most benefit with the least cost attached. Mutability itself is one of the biggest sources for hard-to-follow code, but it also makes a lot of other things much easier. – Corak Apr 23 '16 at 14:15
  • So, following best practices is a good start. Then learn about the costs and benefits they weight against each other and then decide for your specific case, if the costs and benefits weigh differently and if another approach might have a better ratio. – Corak Apr 23 '16 at 14:15
  • It is the same kind of advice as "don't swim after a meal" or "don't skip brushing your teeth before going to bed". You already know what goes wrong when you mutate a struct, so have a swim and a snack. And use a profiler. – Hans Passant Apr 23 '16 at 15:31
  • 1
    @hillin "structs are allocated on the stack instead of the heap" - That is not true in general. `var ds = new DateTime[1000];` allocates 1000 structs on the heap. – Asik Apr 23 '16 at 16:23
  • @Asik sure, that's for referenced structs – hillin Apr 23 '16 at 16:45
  • 2
    The "overhead" of `new Colour` is not what you think it is. For value types, `new Colour` just allocates memory from the stack, and if it is immediately assigned, the JIT can optimize out the allocation and store the result directly into the updated member. TL;DR: Go ahead and do `new Colour` and `new Rect`. It's not as bad as you think. – Raymond Chen Apr 23 '16 at 17:19
  • You can say you're experienced enough to not let it happen, but that still doesn't mean it can't happen. If it does happen, it'll be hard to track down and you'll be shocked you made the mistake. Plus, are you the only one that will ever touch your code? Write your code in a way that will make it difficult for other developers to make a mistake. – Sam Rueby Sep 05 '16 at 12:52

1 Answers1

3

It's not just the chance of accidentally losing information:

list.ForEach(item => item.X = 10);
// this code does nothing useful if item is a mutable struct

It's also the weird interaction between mutating methods and readonly:

readonly MutableStruct m_field;
...
m_field.MutatingMethod(); // mutates a temporary copy rather than the field

But if you've determined through profiling that:

  • You cannot afford reference types, because, say, GC pressure, or you want to put all your objects in an array for better locality
  • You cannot afford to copy the entire struct when you modify it
  • You cannot reasonably change your design to work around these issues (make the structs smaller, have a pool of reference type objects, etc.)
  • You know what you're doing

Then mutable structs may be what you need. That's why they're in the language after all.

Speaking of game code, SharpDX is full of mutable structs (example) and methods that pass by reference.

Asik
  • 21,506
  • 6
  • 72
  • 131