When the constructor initializes the object, it starts with some dummy state. But how do you define a dummy state for an arbitrary object?

The easiest answer is to set all fields to default values: booleans to false, numbers to 0, and reference types to null. But this requires that every type has a default value, and forces the infamous null into the language. This is exactly the path that Java took: at the start of construction, all fields are zero or null.

It’s really hard to paper over this if you want to get rid of null afterwards. A good case study here is Kotlin. Kotlin uses non-nullable types by default, but has to work with pre-exiting JVM semantics. The language-design heroics to hide this fact are really impressive and work well in practice, but are unsound. That is, with constructors it is possible to circumvent Kotlin null-checking.

Kotlin’s main trick is to encourage usage of so-called "primary constructors", which simultaneously declare a field and set it before any user code runs:

1 2 3 4 class Person ( val firstName : String , val lastName : String ) { .. . }

Alternatively, if the field is not declared in the constructor, the programmer is encouraged to immediately initialize it:

1 2 3 class Person ( val firstName : String , val lastName : String ) { val fullName : String = "$firstName $lastName" }

Trying to use a field before initialization is forbidden statically on the best effort basis:

1 2 3 4 5 6 7 class Person ( val firstName : String , val lastName : String ) { val fullName : String init { println ( fullName ) // error: variable must be initialized fullName = "$firstName $lastName" } }

But, with some creativity, one can get around these checks. For example, a method call would do:

1 2 3 4 5 6 7 8 9 10 11 12 class A { val x : Any init { observeNull () x = 92 } fun observeNull () = println ( x ) // prints null } fun main () { A () }

As well as capturing this by a lambda (spelled { args → body } in Kotlin):

1 2 3 4 5 6 7 8 class B { val x : Any = { y }() val y : Any = x } fun main () { println ( B (). x ) // prints null }

Examples like these seem contorted (and they are), but I did hit similar issues in real code (Kolmogorov’s zero–one law of software engineering: in a sufficiently large code base, every code pattern exists almost surely, unless it is statically rejected by the compiler, in which case it almost surely doesn’t exist).

The reason why Kotlin can get away with this unsoundness is the same as with Java’s covariant arrays: runtime does null checks anyway. All in all, I wouldn’t want to complicate Kotlin’s type system to make the above cases rejected at compile time: given existing constraints (JVM semantics), cost/benefit ratio of a runtime check is much better than that of a static check.

What if the language doesn’t have a reasonable default for every type? For example, in C++, where user defined types are not necessary references, one can not just assign nulls to every field and call it a day! Instead, C++ invents special kind of syntactic machinery for specifying initial values of the fields: initializer lists:

1 2 3 4 5 6 7 8 9 10 11 12 #include <string> #include <utility> class person { person ( std :: string first_name , std :: string last_name ) : first_name ( std :: move ( first_name )) , last_name ( std :: move ( last_name )) {} std :: string first_name ; std :: string last_name ; };