Value types not having parameterless constructors…
Posted by jpluimers on 2019/03/27
The list below is based on a G+ discussion in a single language, but has way broader aspects.
It’s on value types, mutability, parameterless constructors and expectations of compiled code.
I’ve bitten myself in the foot with mutable types in too many languages too often, so I started advocating this years ago at clients, and now in this blog-post.
TL;DR:
- some languages disallow parameterless constructors: C++, C# and Delphi are examples
- this historically stems from the C++ and C# background
- it has to do with them not wanting to automatically call them upon array initialisation taking a lot of CPU time
- most languages do not stop you from making mutable value types, but in practice your value types should be immutable as otherwise you will open a can of worms, for instance you will have a hard time:
- preventing threading issues
- making code following functional patterns
- scaling your code by making your algorithms supporting parallel execution
- parameterless constructors include constructors with parameters having default values
Some links that explain this in more depth:
- [WayBack] c# – const, readonly and mutable value types – Stack Overflow
- [WayBack] c# – Why can’t I define a default constructor for a struct in .NET? – Stack Overflow (Thanks Jon Skeet)
- [WayBack] c# – const, readonly and mutable value types – Stack Overflow (thanks vc 74)
- [WayBack] Parameterless constructors on record types: I wanted to have a constructor on a record type that would set default values for the elements… – Girish Patil – Google+
- [WayBack] c# – Why are mutable structs “evil”? – Stack Overflow
- [WayBack] delphi – Why are parameterless constructors not allowed on records? – Stack Overflow
- [WayBack] Delphi instance methods and value types – twm’s blog via [WayBack] David Heffernan commented on Girish Patil’s post on G+ But you should never make instance methods on value types that mutate the value. Otherwise you … – Thomas Mueller (dummzeuch) – Google+ (this is what started me to collect this overview)
The “just pass them as reference” often seen as reason to explain “mutable value types are OK” is exactly describing why they are not OK.
--jeroen






rvelthuis said
I don’t quite see what default constructors would have to do with immutable types. Even immutable types may need initialization and finalization, e.g. if they contain a dynarray.
rvelthuis said
“some languages disallow parameterless constructors: C++, C# and Delphi are examples”.
Wrong!
C++ does allow them alright, also for structs. They are actually the default constructor, and every C++ class/struct has those, and if they are not declared explicitly, the compiler generates them. And Delphi was meant to get those too, for Rio, along with copy constructors, destructors, overloaded assignment operators, just like C++. And they were supposed to work the same way as in C++. But there were some problems with the codegen, so that feature was postponed.
“It has to do with them not wanting to automatically call them upon array initialisation taking a lot of CPU time”
Not at all. Anyone defining them should be aware they might be called even in huge arrays, once for each element. If you don’t want that, you simply don’t define them.
Their huge benefit would be that you could write your own initialization code, and that _InitializeRecord(ArrayPointer, Typeinfo) will not be called anymore (this runtime function is slow, as it must find and initialize every managed field at runtime, by iterating over the type info — this takes an enormous amount of time, wasted time). The same for _CopyRecord and _FinalizeRecord. These functions are much slower than a simple default constructor/destructor/copy constructor/assignment would be and these functions are also called for each element of an array, no matter how huge.
Delphi does not allow them right now because they would cause problems for C++Builder (just like named constructors can cause problems for C++Builder). In C++, default constructors are called automatically for every declared struct, even those in a huge array. But Delphi’s wouldn’t be, and that would cause big inconsistencies. As soon as they are allowed (and automatically called), that problem will be gone.
There is a reason they are not allowed in C#. I’ve seen the explanation somewhere (I think it was from Eric Lippert), but don’t remember where I’ve seen it. It was not the same as the one for Delphi.