Reopening the debate about non-nullable-by-default: initialization of member fields

Idan Arye via Digitalmars-d digitalmars-d at puremagic.com
Fri May 2 17:50:14 PDT 2014


We are all sick and tired of this debate, but today I've seen a 
question in Stack Exchange's Programmers board that raises a 
point I don't recall being discussed here:

http://programmers.stackexchange.com/questions/237749/how-do-languages-with-maybe-types-instead-of-nulls-handle-edge-conditions

Consider the following code:

     class Foo{
         void doSomething(){
         }
     }

     class Bar{
         Foo foo;

         this(Foo foo){
             doSomething();
             this.foo=foo;
         }

         void doSomething(){
             foo.doSomething();
         }
     }

Constructing an instance of `Bar`, of course, segfaults when it 
calls `doSomething` that tries to call `foo`'s `doSomething`. The 
non-nullable-by-default should avoid such problems, but in this 
case it doesn't work since we call `doSomething` in the 
constructor, before we initialized `foo`.

Non-nullable-by-default is usually used in functional languages, 
where the emphasis on immutability requires a syntax that always 
allow initialization at declaration, so they avoid this problem 
elegantly. This is not the case in D - member fields are declared 
at the body of the class or struct and initialized at the 
constructor - separate statements that nothing stops you from 
putting other statements between them.

Of course, D does support initialization at declaration for 
member fields, but this is far from sufficient a solution since 
very often the information required for setting the member field 
resides in the constructor's arguments. In the example, we can't 
really initialize `foo` at the declaration since we are supposed 
to get it's initial value in the constructor's argument `Foo foo`.



I can think of 3 solutions - each with it's own major drawback 
and each with a flaw that prevents it from actually solving the 
problem, but I'll write them here anyways:

1) Using a static analysis that probes into function calls. The 
major drawback is that it'll probably be very hard to implement. 
The reason it won't work is that it won't be able to probe into 
overriding methods, which might use an uninitialized member field 
that the overrided method doesn't use.

2) Disallow calling functions in the constructor before *all* 
non-nullable member fields are initialized(and of course, the 
simple static analysis that prevent usage before initialization 
directly in the constructor code). The major drawback is that 
sometimes you need to call a function in order to initialize the 
member field. The reason is best demonstrated with code:
     class Foo{
         void doSomething(){
         }
     }

     class Bar{
         this{
             doSomething();
         }

         void doSomething(){
         }
     }

     class Baz : Bar{
         Foo foo;

         this(Foo foo){
             this.foo=foo;
         }

         override void doSomething(){
             foo.doSomething();
         }
     }
`Bar`'s constructor is implicitly called before `Baz`'s 
constructor.

3) Use a Scala-like syntax where the class' body is a constructor 
that all other constructors must call, allowing initialization on 
declaration for member fields in all cases. The major drawback is 
that this is a new syntax that'll have to be used in order to 
have non-nullable member fields - which means it'll break almost 
every existing code that uses classes. Not fun. The reason it 
won't work is that declarations in the struct\class' body are not 
ordered. In Scala, for example, this compiles and breaks with 
null pointer exception when trying to construct `Bar`:
     class Foo{
         def doSomething(){
         }
     }

     class Bar(foo : Foo){
         doSomething();
         val m_foo=foo;

         def doSomething(){
             m_foo.doSomething();
         }
     }
Also, like the previous two methods, overriding methods breaks 
it's promises.



This issue should be addressed before implementing 
non-nullable-by-default.


More information about the Digitalmars-d mailing list