An immutable object never changes. You can bet your program on it. As I explained in my previous post, the same is not true for const objects (or readonly objects, in dialects of Java). They may be changed through mutable aliases. An immutable object has no mutable aliases. Ever!
Small print: this guarantee is predicated on the programmer not overriding the type system with casts and other escape mechanisms.
To my knowledge, immutability is currently available in the D programming language and in a Java dialect called IGJ (Immutability Generic Java). It is the default in functional languages.
The closest you can get to immutability in C++ is by using const as a storage class:
const double pi = 3.141592; const char ERRORMSG[] = "Your bank went belly up.";
Here, the value of pi or ERRORMSG is guaranteed to never change. Global or static immutable values can be used at compile time (for instance as labels in a switch statement).
Creating Immutable Objects
Defining immutable numbers, arrays, or POD structs is pretty straightforward. But what about more complex objects that don’t have static initializers? How do you create an immutable linked list? In functional languages, lists are treated as built-in types; like arrays in general purpose languages. They can be statically initialized. But in C++ or Java the creation of a list involves memory allocations and reference manipulation. Since, by definition, we can’t manipulate an immutable list, it seems like we can never create one!
How about relaxing the constraints a little to allow mutation inside the constructor of an immutable object. Here’s a hypothetical example of a list in D (the current D compiler doesn’t fully implement immutability, so my examples are written in pseudo-D):
class IntList { public: // default constructor this() {} // _head is default-initialized to null // one element constructor this(int i) { _head = new IntLink(i); // mutates _head } private: IntLink _head; } // will this work? immutable IntList oneList= new immutable IntList(1);
There is a significant problem with this solution. If this is not considered immutable inside the constructor then there is no guarantee that a mutable reference to this or any of its subobjects won’t escape its scope. Consider this example:
IntLink globalLink; // mutable!
IntList.this(int i) {
_head = new IntLink(i);
globalLink = _head; // escape!
}
immutable IntList immList= new immutable IntList(1);
globalLink.setValue(2); // mutates immList!
Here, an immutable object immList has been mutated through an alias globalLink. We can’t allow this!
It’s true that a compiler could perform escape analysis on the constructor of IntList, provided it has access to its source code; which might not always be true when it’s compiling the statement that creates the immutable object. After all, class IntList might be implemented in a third-party library.
In the absence of source code, the only other possibility is to include immutability information in the type signature of the constructor. When an immutable object is created, the compiler would use an immutable constructor, and it would fail if one didn’t exist. Conversely, an immutable constructor would not compile if it allowed a mutable reference to escape. This bad code would not compile:
IntList.this(int i) immutable { _head = new IntLink(i); globalLink = _head; // error! }
Of course, no mutable methods may be called from inside an immutable constructor–they couldn’t guarantee the non-escape of mutable aliases.
This solution works, even if it’s not perfect. It often leads to code duplication (the immutable constructor being identical to the mutable one, as in the IntList example). Moreover, it prevents some forms of refactoring. Even though, inside an immutable constructor, you may initialize an object’s fields, you can’t delegate this task to a (perforce immutable) method of the object.
Assignment is not the same as Mutation
The key to immutable construction is the observation that, when constructing an immutable object, it’s okay to assign the object’s fields but not to mutate them. During construction only such “shallow” mutation should be possible.
In my example, the assignment to _head is okay, but the mutation of the IntLink object attached to it should be prohibited. Indeed, I don’t need to mutate the head link once it’s constructed. Of course the construction of an immutable IntLink follows the same rules. Here’s the relevant code:
class IntLink { this(int i) immutable { // _next is default-initialized to null _val = i; // field assignment } int _val; IntLink _next; }
With this understanding, it’s actually possible to minimize code duplication. To this end, IGJ introduces a new type modifier, AssignFields. A constructor or a method that performs no other mutation but field assignment may be declared AssignFields. Since AssignFields methods and AssignFields constructors can also be used in mutable contexts, they don’t have to be duplicated. Expanding on the above example:
class IntLink { this(int i) assignfields { // _next is default-initialized to null SetValue(i); // Ok: it's an assignfields method } void SetValue(int i) assignfields { _val = i; // field assignment } int _val; IntLink _next; }
As you can see, I was even able to refactor the part of the constructor that does the assignment to _val. I can now use the same constructor in both, mutable and immutable, contexts. The SetValue method can only be called in a mutable or assignfields context.
immutable IntLink iLink = new immutable IntLink(1); IntLink mLink = new IntLink(2); mLink.SetValue(3);
Subtyping relationships
It is okay to pass an immutable object to a function that expects a const object. After all, such a function will not mutate the object. If a reference to the object escapes the function, it can only be a const reference. And again, const reference cannot be used to mutate the object, so we’re fine.
The compiler will allow this subsumption if we establish that immutable is a subtype of const (more precisely, for any type T, immutable T is a subtype of const T). This is very similar to the compiler allowing the passing a derived class object to a function that accepts a base class objects–the Liskov substitution principle.
The full subtyping hierarchy between various mutability annotations is as follows:
- immutable is a subtype of const: You can pass an immutable object to a function taking a const argument.
- assignfields is a subtype of const: You can call a const method from an assignfields method (subsumption of this).
- mutable is a subtype of assignfields. You can call an assignfields method on a mutable object (as in mLink.SetValue()).
Because of transitivity, mutable is also a subtype of const. You can pass a mutable object to a function that takes a const argument (you do it in C++ without much thinking).
Conclusion
There is some advantage to introducing yet another type modifier, assignfields, to smooth out the process of constructing immutable objects. On the other hand, is it worth additional complexity? How often does one construct non-trivial immutable objects? If it’s not a common programming pattern then maybe we can live with current restrictions and some code duplication. We still have very little experience in using immutable in D, so we might have to wait and see.
Recent news: A video of my talk to the Vancouver C++ Users group was just posted. The black shadow in front of the bright screen talking about memory fences is yours truly.
If you found this post interesting, please register your vote on reddit, so that more people can find it.
February 6, 2009 at 7:57 pm
I read this over at the AMD blog about creating immutable objects using builders. I found it interesting so I would like to read your take on it.
http://forums.amd.com/devblog/blogpost.cfm?catid=313&threadid=108340