Immutability of Structs

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.

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.

How do I make a struct immutable?

Make the fields private readonly and pass the initial values in the constructor

public struct ImmutableStruct
{
private readonly int _field1;
private readonly string _field2;
private readonly object _field3;

public ImmutableStruct(int f1, string f2, object f3)
{
_field1 = f1;
_field2 = f2;
_field3 = f3;
}

public int Field1 { get { return _field1; } }
public string Field2 { get { return _field2; } }
public object Field3 { get { return _field3; } }
}

Starting with C#6.0 (Visual Studio 2015) You can use getter only properties

public struct ImmutableStruct
{
public ImmutableStruct(int f1, string f2, object f3)
{
Field1 = f1;
Field2 = f2;
Field3 = f3;
}

public int Field1 { get; }
public string Field2 { get; }
public object Field3 { get; }
}

Note that readonly fields and getter only properties can be initialized either in the constructor or, in classes, also with field or property initializers public int Field1 { get; } = 7;.

It is not guaranteed that the constructor is run on a struct. E.g. if you have an array of structs, you must then call the initializers explicitly for each array element. For arrays of reference types all the elements are first initialized to null, which makes it obvious that you have to call new on each element. But it is easy to forget it for value types like structs.

var immutables = new ImmutableStruct[10];
immutables[0] = new ImmutableStruct(5, "hello", new Person());
immutables[1] = new ImmutableStruct(6, "world", new Student());
...

Starting with C# 7.2, you can use Read-only structs


Starting with C# 9.0 there is yet another option: the Init-Only Properties. Read-only fields and get-only auto implemented properties can be initialized in a constructor and in the field or property initializer but not in an object initializer.

This is the motivation for introducing init-only properties. They replace the set accessor by an init accessor. This extends the mutation phase from the actual object creation to the entire object construction phase including object initializers and with expressions (also a new C# 9.0 feature).

public string Name { get; init; }

Usage:

var x = new ImmutableStruct { Name = "John" }; // Okay

x.Name = "Sue"; // Compiler error CS8852: Init-only property or indexer
// 'ImmutableStruct.Name' can only be assigned in an object
// initializer, or on 'this' or 'base' in an instance constructor
// or an 'init' accessor.

C# 10.0 (Visual Studio 2022) introduces Record structs, Parameterless struct constructors and field initializers.

readonly record struct Point(int X, int Y);

This generates X and Y properties with get and init accessors.

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)

Immutable structure in c#

Structs in C# are not immutable by default. To mark a struct immutable, you should use the readonly modifier on the struct. The reason your properties aren't updating is that your syntax is wrong. You should use the value keyword to dereference the value provided to the setter.

public int Age
{
get
{
return age;
}
set
{
age = value; // <-- here
}
}

Immutable Struct in Golang

It is possible to make a struct read-only outside of its package by making its members non-exported and providing readers. For example:

package mypackage

type myReadOnly struct {
value int
}

func (s myReadOnly) Value() int {
return s.value
}

func NewMyReadonly(value int) myReadOnly{
return myReadOnly{value: value}
}

And usage:

myReadonly := mypackage.NewMyReadonly(3)
fmt.Println(myReadonly.Value()) // Prints 3

How to make struct immutable inside a class definition

It's a little difficult to give a complete answer without understanding exactly what you are trying to accomplish, but I'll start with a few important distinctions.

First, in C#, the struct/class distinction isn't about mutability per se. You can have a immutable class, like this one

public class CannotBeMutated
{
private string someVal;
public CannotBeMutated(string someVal)
{
_someVal = someVal
}

public string SomeVal => _someVal;
}

and a mutable struct, like this one

// This is not at all idiomatic C#, please don't use this as an example
public struct MutableStruct
{
private string _someVal;
public MutableStruct(string someVal)
{
_someVal = someVal;
}

public void GetVal()
{
return _someVal
}

public void Mutate(string newVal)
{
_someVal = newVal;
}
}

Using the above struct I can do this

 var foo = new MutableStruct("Hello");
foo.mutate("GoodBye");
var bar = foo.GetVal(); // bar == "GoodBye"!

The difference between structs and classes is in variable passing semantics. When an object of a value type (e.g. a struct) is assigned to a variable, passed as a parameter to or returned from a method (including a property getter or setter) a copy of the object is made before it is passed to the new function context. When a object of a reference type is passed as a parameter to or returned from a method, no copy is made, because we only pass a reference to the object's location in memory, rather than a copy of the object.

An additional point on struct 'copying'. Imagine you have a struct with a field that is a reference type, like this

public struct StructWithAReferenceType
{
public List<string> IAmAReferenceType {get; set;}
}

When you pass an instance of this struct into a method, a copy of the reference to the List will be copied, but the underlying data will not. So if you do

public void MessWithYourSruct(StructWithAReferenceType t)
{
t.IAmAReferenceType.Add("HAHA");
}

var s = new StructWithAReferenceType { IAmAReferenceType = new List()};
MessWithYourSruct(s);
s.IAmAReferenceType.Count; // 1!

// or even more unsettling
var s = new StructWithAReferenceType { IAmAReferenceType = new List()};
var t = s; // makes a COPY of s
s.IAmAReferenceType.Add("hi");
t.IAmAReferenceType.Count; // 1!

Even when a struct is copied, its reference type fields still refer to the same objects in memory.

The immutable/mutable and struct/class differences are somewhat similar, insofar as they are both about where and whether you can change the contents of an object in your program, but they are still very distinct.

Now on to your question. In your second example, Class1 is not immutable, as you can mutate the value of Struct2 like this

var foo = new Class1();
foo.Struct2 = new Struct2("a", 1);
foo.Struct2 // returns a copy of Struct2("a", 1);
foo.Struct2 = new Struct2("b", 2);
foo.Struct2 // returns a copy of Struct2("b", 2);

Struct2 is immutable, as there is no way for calling code to change the values of StrVar or InVar once. InStruct is similarly immutable. However, Array is not immutable. So InStruct is an immutable container for a mutable value. Similar to if you had a ImmutableList<List<string>>. While you can guarantee calling code does not change the value of InStruct.StrArray to a different array, you can do nothing about calling code changing the value of the objects in the Array.

Finally, some generic advice related to your example.

First, mutable structs, or structs with mutable fields, are bad. The examples above should point to why structs with mutable fields are bad. And Eric Lippert himself has a great example of how terrible mutable structs can be on his blog here

Second, for most developers working in C# there's almost never a reason to create a user defined value type (i.e. a struct). Objects of value types are stored on the stack, which makes memory access to them very fast. Objects of reference types are stored on the heap, and so are slower to access. But in the huge majority of C# programs, that distinction is going to be dwarfed by the time cost of disk I/O, network I/O, reflection in serialization code, or even initialization and manipulation of collections. For ordinary developers who aren't writing performance-critical standard libraries, there's almost no reason to think about the performance implications of the difference. Heck, developers in Java, Python, Ruby, Javascript and many other languages get by in languages totally without user-defined value types. Generally, the added cognitive overhead they introduce for developers is almost never worth any benefit you might see. Also, remember that large structs must be copied whenever they are passed or assigned to a variable, and can actually be a performance problem.

TL;DR you probably shouldn't use structs in your code, and they don't really have anything to do with immutability.

Immutable class vs struct

Since copying reference is cheaper than copying struct, why would one use an immutable struct?

This isn't always true. Copying a reference is going to be 8 bytes on a 64bit OS, which is potentially larger than many structs.

Also note that creation of the class is likely more expensive. Creating a struct is often done completely on the stack (though there are many exceptions), which is very fast. Creating a class requires creating the object handle (for the garbage collector), creating the reference on the stack, and tracking the object's lifetime. This can add GC pressure, which also has a real cost.

That being said, creating a large immutable struct is likely not a good idea, which is part of why the Guidelines for choosing between Classes and Structures recommend always using a class if your struct will be more than 16 bytes, if it will be boxed, and other issues that make the difference smaller.

That being said, I often base my decision more on the intended usage and meaning of the type in question. Value types should be used to refer to a single value (again, refer to guidelines), and often have a semantic meaning and expected usage different than classes. This is often just as important as the performance characteristics when making the choice between class or struct.

How to create immutable structs in C++?

The reason for this is that in C++ after a variable has been created (either a local or field variable) it cannot be "replaced" with another instance, only its state can be altered. So in this line

index = MyIndex(1);

not a new MyIndex instance is created (with its constructor), but rather the existing index variable is supposed to be altered with its copy assignment operator. The missing operator = function is the copy assignment operator of the MyIndex type, which it lacks, because it doesn't get automatically generated if the type has a const field.

The reason why it is not generated is that it simply can't be sensibly implemented. A copy assignment operator would be implemented like this (which doesn't work here):

MyIndex& operator=(const MyIndex& other)
{
if(this != &other)
{
Value = other.Value; // Can't do this, because Value is const!
}

return *this;
}

So if we would like our structs to be practically immutable, but behave similarly to immutable structs in C#, we have to take another approach, which is to make every field of our struct private, and mark every function of our class with const.

The implementation of MyIndex with this approach:

struct MyIndex
{
MyIndex(int value) :
value(value)
{
}

// We have to make a getter to make it accessible.
int Value() const
{
return value;
}

private:
int value;
};

Strictly speaking the MyIndex struct is not immutable, but its state cannot be altered with anything accessible from outside (except its automatically generated copy assignment operator, but that's what we wanted to achieve!).

Now the above code examples compile properly, and we can be sure that our variables of type MyIndex won't be mutated, unless they are completely replaced by assigning a new value to them.



Related Topics



Leave a reply



Submit