Andrei Alexandrescu wrote:
> Walter Bright wrote:
>> The problem with non-nullable references is what do they default to? Some "nan" object? When you use a "nan" object, what should it do? Throw an exception?
> > This is the mistake. There would no way to default initialize a non-null object. I'm surprised you are still saying this, because we discussed how NonNull!T could be implemented by disabling its default constructor.
Sure, so the user just provides "0" as the argument to the non-default constructor. Or he writes:
C c = c_empty;
using c_empty as his placeholder for an empty object. Now, what happens with:
c.foo();
? Should c_empty throw an exception? To take this a little farther, suppose I wish to create an array of C that I will partially fill with valid data, and leave some empty slots. Those empty slots I stuff with c_empty, to avoid having nulls. What is c_empty's proper behavior if I mistakenly try to access its members?
Forcing the user to provide an initializer does not solve the problem. The crucial point is the problem is *not* the seg fault, the seg fault is the symptom. The problem is the user has not set the object to a value that his program's logic requires.
I am also perfectly happy with NonNull being a type constructor, to be used where appropriate. My disagreement is with the notion that null references should be eliminated at the language level.

Walter Bright wrote:
> Andrei Alexandrescu wrote:
>> Walter Bright wrote:
>>> The problem with non-nullable references is what do they default to? Some "nan" object? When you use a "nan" object, what should it do? Throw an exception?
>>>> This is the mistake. There would no way to default initialize a non-null object. I'm surprised you are still saying this, because we discussed how NonNull!T could be implemented by disabling its default constructor.
> > Sure, so the user just provides "0" as the argument to the non-default constructor. Or he writes:
> > C c = c_empty;
> > using c_empty as his placeholder for an empty object. Now, what happens with:
> > c.foo();
> > ? Should c_empty throw an exception?
The problem is you keep on insisting on one case "I have a non-null reference that I don't have an initializer for, but the compiler forces me to find one, so I'll just throw a crappy value in." This focus on one situation comes straight with your admitted bad habit of defining variables in one place and initializing in another. The situation you need to open a curious eye on is "I have a reference that's never supposed to be null, but I forgot about initializing it and the compiler silently put a useless null in it." The simplest case is what _every_ D beginner has done:
T x;
x.fun();
to witness a crash. Why the hell does that crash? It did work when T was a struct. (Also this damns generic code to hell.)
So again: focus on the situation when people forget to initialize references that are never supposed to be null.
That has happened to me, and I'm supposed to know about this stuff. And one thing you don't understand is that on Linux, access violations are much more difficult to figure than others. On a computing cluster it gets one order of magnitude more difficult. So spare me of your Windows setup that launches your debugger on the line of the crash. For better or worse, many don't have that. People sometimes have problems that you don't have, and you need to put yourself in their shoes.
> To take this a little farther, suppose I wish to create an array of C that I will partially fill with valid data, and leave some empty slots. Those empty slots I stuff with c_empty, to avoid having nulls. What is c_empty's proper behavior if I mistakenly try to access its members?
You make an array of nullable references. Again you confuse having non-null as a default with having non-null as the only option.
> Forcing the user to provide an initializer does not solve the problem. The crucial point is the problem is *not* the seg fault, the seg fault is the symptom. The problem is the user has not set the object to a value that his program's logic requires.
> > > I am also perfectly happy with NonNull being a type constructor, to be used where appropriate. My disagreement is with the notion that null references should be eliminated at the language level.
Null references shouldn't be eliminated from the language. They just should NOT be the default. I guess I'm going to say that until you tune on my station.
Andrei

Walter Bright wrote:
> ...
> > It all depends on what you prefer a program to do when it encounters a program bug:
> > 1. Immediately stop and produce an indication that the program failed
> > 2. Soldier on and silently produce garbage output
> > I prefer (1).
> > ...
*sigh* Walter, I really admire you as a programmer. But this is about the most blatant strawman argument I've seen in a while.
Firstly, as others have indicated, the whole point of non-null would be to REQUIRE initialisation to something useful.
"But the user will just assign to something useless to get around that!"
You mean like how everyone wraps every call in try{...}catch(Exception e){} to shut the damn exceptions up? Or uses pointer arithmetic and casts to get at those pesky private members?
If someone is actively trying to break the type system, it's their goddamn fault! Honestly, I don't care about the hacks they employ to defeat the system because they're going to go around blindly shooting themselves in the foot no matter what they do.
It's like arguing that safety rails are pointless because people can just jump over them. BESIDES, if they fall off, you get this really loud "crunch" followed by a shower of blood; then it's OBVIOUS that something's wrong.
And what about the people who AREN'T complete idiots, who maybe sometimes just accidentally trip and would quite welcome a safety rail there?
Besides which, the non-null proposal is always accompanied by the proposal to add nullable object references as T? (or whatever; the syntax is irrelevant at this point). If a programmer really wants a null-initialised object reference, which is she more likely to do?
class NullFoo : Foo
{
void member1() { throw new Exception("NULL!"); }
void member2() { throw new Exception("NULL!"); }
...
}
Foo bar = new NullFoo;
or
Foo? bar;
Since the reason they're trying to circumvent the non-null protection is because of laziness, I assert they're far more likely to go with the second than the first.
And it's STILL better because you couldn't implicitly cast between Foo? and Foo. They would HAVE to insert an explicit cast or check.
Foo quxx = enforceNN(bar);
Finally, let me re-post something I wrote the last time this came up:
> The problem with null dereference problems isn't knowing that they're there: that's the easy part. You helpfully get an exception to the face when that happens. The hard part is figuring out *where* the problem originally occurred. It's not when the exception is thrown that's the issue; it's the point at which you placed a null reference in a slot where you shouldn't have.
>> Yes, we have invariants and contracts; but inevitably you're going to forget one, and it's that one slip-up that's going to bite you in the rear.
>> One could use roughly the same argument for non-null references as for const: you could document it, but documentation is inevitably wrong or out of date. :P

Andrei Alexandrescu wrote:
> [snip]
> The problem is you keep on insisting on one case "I have a non-null reference that I don't have an initializer for, but the compiler forces me to find one, so I'll just throw a crappy value in." This focus on one situation comes straight with your admitted bad habit of defining variables in one place and initializing in another. The situation you need to open a curious eye on is "I have a reference that's never supposed to be null, but I forgot about initializing it and the compiler silently put a useless null in it." The simplest case is what _every_ D beginner has done:
> > T x;
> x.fun();
> > to witness a crash. Why the hell does that crash? It did work when T was a struct. (Also this damns generic code to hell.)
> > So again: focus on the situation when people forget to initialize references that are never supposed to be null.
> > That has happened to me, and I'm supposed to know about this stuff. And one thing you don't understand is that on Linux, access violations are much more difficult to figure than others. On a computing cluster it gets one order of magnitude more difficult. So spare me of your Windows setup that launches your debugger on the line of the crash. For better or worse, many don't have that. People sometimes have problems that you don't have, and you need to put yourself in their shoes.
Quoted for truth.
--
Tomasz Stachowiak
http://h3.team0xf.com/
h3/h3r3tic on #D freenode

Hello Walter,
> The problem with non-nullable references is what do they default to?
> Some "nan" object? When you use a "nan" object, what should it do?
> Throw an exception?
>
They don't have a default. There semantics would be such that the compiler rejects as illegal any code that would require it to supply a default.
As to the user stuffing "c_empty" in just to get the compiler to shut up; firstly, that says the variable should not yet be declared as you don't yet known what value to give it and secondly either c_empy is a rational value or the user is subverting the type system and is on there own.

Walter Bright wrote:
> Denis Koroskin wrote:
> > On Sat, 26 Sep 2009 22:30:58 +0400, Walter Bright
> > <newshound1@digitalmars.com> wrote:
> >> D has borrowed ideas from many different languages. The trick is to
> >> take the good stuff and avoid their mistakes <g>.
> >
> > How about this one:
> > http://sadekdrobi.com/2008/12/22/null-references-the-billion-dollar-mistake/> > >
> >
> > :)
> > I think he's wrong.
Please, please, please, do some fun little project in Java or C# and drop the idea of initializing variables whenever you declare them. Just leave them like this:
int i;
and then later initialize them when you need them, for example different values depending on some conditions. Then you'll realize how powerful is having the compiler stop variables that are not initialized *in the context of a function, not necessarily in the same line of their declaration*. It's always a win: you get a compile time error, you don't have to wait to get an error at runtime.
Until you do that, you won't understand what most people are answering to you.
But I know what you'll answer. You'll say "what about pointers?", "what about ref parameters?", "what about out parameters?", and then someone will say to you "C# has them", etc, etc.
No point disussing non-null variables without also having the compiler stop uninitialized variables.

Daniel Keep wrote:
> "But the user will just assign to something useless to get around that!"
> > You mean like how everyone wraps every call in try{...}catch(Exception
> e){} to shut the damn exceptions up?
They do just that in Java because of the checked-exceptions thing. I have a reference to Bruce Eckel's essay on it somewhere in this thread. The observation in the article was it wasn't just moron idiot programmers doing this. It was the guru programmers doing it, all the while knowing it was the wrong thing to do. The end result was the feature actively created the very problems it was designed to prevent.
> Or uses pointer arithmetic and
> casts to get at those pesky private members?
That's entirely different, because privacy is selected by the programmer, not the language. I don't have any issue with a user-defined type that is non-nullable (Andrei has designed a type constructor for that).
> If someone is actively trying to break the type system, it's their
> goddamn fault! Honestly, I don't care about the hacks they employ to
> defeat the system because they're going to go around blindly shooting
> themselves in the foot no matter what they do.
True, but it's still not a good idea to design a language feature that winds up, in reality, encouraging bad programming practice. It encourages bad practice in a way that is really, really hard to detect in a code review.
I like programming mistakes to be obvious, not subtle. There's nothing subtle about a null pointer exception. There's plenty subtle about the wrong default value.
> And what about the people who AREN'T complete idiots, who maybe
> sometimes just accidentally trip and would quite welcome a safety rail
> there?
Null pointer seg faults *are* a safety rail. They keep an errant program from causing further damage.
> Finally, let me re-post something I wrote the last time this came up:
> >> The problem with null dereference problems isn't knowing that they're
>> there: that's the easy part. You helpfully get an exception to the
>> face when that happens. The hard part is figuring out *where* the
>> problem originally occurred. It's not when the exception is thrown
>> that's the issue; it's the point at which you placed a null reference
>> in a slot where you shouldn't have.
It's a lot harder to track down a bug when the bad initial value gets combined with a lot of other data first. The only time I've had a problem finding where a null came from (because they tend to fail very close to their initialization point) is when the null was caused by another memory corruption problem. Non-nullable references won't mitigate that.

Walter Bright wrote:
> Daniel Keep wrote:
>> "But the user will just assign to something useless to get around that!"
>>>> You mean like how everyone wraps every call in try{...}catch(Exception
>> e){} to shut the damn exceptions up?
> > They do just that in Java because of the checked-exceptions thing. I have a reference to Bruce Eckel's essay on it somewhere in this thread. The observation in the article was it wasn't just moron idiot programmers doing this. It was the guru programmers doing it, all the while knowing it was the wrong thing to do. The end result was the feature actively created the very problems it was designed to prevent.
> > >> Or uses pointer arithmetic and
>> casts to get at those pesky private members?
> > That's entirely different, because privacy is selected by the programmer, not the language. I don't have any issue with a user-defined type that is non-nullable (Andrei has designed a type constructor for that).
> > >> If someone is actively trying to break the type system, it's their
>> goddamn fault! Honestly, I don't care about the hacks they employ to
>> defeat the system because they're going to go around blindly shooting
>> themselves in the foot no matter what they do.
> > True, but it's still not a good idea to design a language feature that winds up, in reality, encouraging bad programming practice. It encourages bad practice in a way that is really, really hard to detect in a code review.
> > I like programming mistakes to be obvious, not subtle. There's nothing subtle about a null pointer exception. There's plenty subtle about the wrong default value.
> > >> And what about the people who AREN'T complete idiots, who maybe
>> sometimes just accidentally trip and would quite welcome a safety rail
>> there?
> > Null pointer seg faults *are* a safety rail. They keep an errant program from causing further damage.
Null pointer seg faults *not being able to happen* are much more safe. :)