digitalmars.D - Reopening the debate about non-nullable-by-default: initialization of
- Idan Arye (97/97) May 02 2014 We are all sick and tired of this debate, but today I've seen a
- bearophile (20/22) May 02 2014 This program:
- Jonathan M Davis via Digitalmars-d (16/40) May 02 2014 Yeah, I brought this up before, and it's one of the reasons why I'm agai...
- bearophile (9/15) May 03 2014 I think that problem was raised plenty of times during the
- w0rp (4/4) May 03 2014 I would allow this bug. This also happens with 'final' member in
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.
May 02 2014
Idan Arye:today I've seen a question in Stack Exchange's Programmers board that raises a point I don't recall being discussed here:This program: class A { immutable int x; this() { foo(); x = 1; x = 2; } void foo() { auto y = x; } } void main() {} Gives: temp.d(6,9): Error: immutable field 'x' initialized multiple times So D can tell x is initialized more than 1 time, but it can't tell x is initialized 0 times inside foo(). Bye, bearophile
May 02 2014
On Sat, 03 May 2014 00:50:14 +0000 Idan Arye via Digitalmars-d <digitalmars-d puremagic.com> wrote: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`.Yeah, I brought this up before, and it's one of the reasons why I'm against non-nullable by default. It means that class references will need to be treated the same as structs whose init property is disabled, which can be _very_ limiting. And I don't know if we currently handle structs with disabled init properties correctly in all cases, since it's not all that hard for something subtle to have been missed and allow for such a struct to be used before it was actually initialized (and the fact that not much code uses them would make it that much more likely that such a bug would go unnoticed). Hopefully, all those issues have been sorted out by now though. If so, then I would think that we already have all of the rules in place for how non-nullable references would be dealt with with regards to initialization, but they'd still be very limiting, because most of D expects that all types have an init property. - Jonathan M Davis
May 02 2014
Jonathan M Davis:Idan Arye: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:Yeah, I brought this up before, and it's one of the reasons why I'm against non-nullable by default.I think that problem was raised plenty of times during the discussions about non-nullable. And I think this paper (and successive ones) solve enough the problems: http://research.microsoft.com/en-us/um/people/leino/papers/krml109.pdf And aren't the ideas about cooked/uncooked (referred as raw in the paper) already partially present in D? Bye, bearophile
May 03 2014
I would allow this bug. This also happens with 'final' member in Java, and there you expect the value to not be null. A rule of thumb should be to never call virtual methods from inside of a constructor. Bad things happen.
May 03 2014