Void safety: Getting rid of the spectre of null-pointer dereferencing
A spectre is haunting programming — the spectre of null-pointer dereferencing. All the programming languages of old Europe and the New World have entered into a holy alliance to make everyone’s programs brittle: Java, C, Pascal, C++, C# and yes, until recently, Eiffel.
The culprit is the use of references to denote objects used in calls: in
x.f (...)
the value of x is a reference, which normally denotes an object but could at any time be void (or “null”). If this happens, the resulting “void call” will cause an exception and, usually, a crash. No amount of testing can remove the risk entirely; the only satisfactory solution is a static one, enforcing void safety at the language level.
To this end, Eiffelists of various nationalities have assembled in the Cloud and sketched the following manifesto, to be published in the English language:
Avoid a Void: The Eradication of Null Dereferencing Bertrand Meyer, Alexander Kogtenkov, Emmanuel Stapf White paper available here.
I think page 2 overstates the success of type safety. “By adopting static typing, most modern O-O languages have ruled out type mismatch, the other potential source of run-time failure.”
Although it’s true that Eiffel has all but eliminated cat-calls, which were never really a problem in practice anyway, programming languages like Java, C# and Visual Basic are still plagued by exceptions caused by invalid type casts. Eiffel programmers were always insulated from type-mismatch exceptions by the old (and now obsolete) assignment attempt mechanism, which substituted the risk of an invalid type cast with the risk of a void call. With the advent of void-safety, in one fell swoop Eiffel has eliminated what C# programmers still know (and dread) as the NullReferenceException and, sadly too, the InvalidTypeCastException.
There is indeed a way to make a language “type-safe” on paper, by passing the buck to every individual programmer. That’s often what type casts do. In the design of Eiffel we have tried to avoid this kind of solution.
To be fair, C# provided a mechanism similar to Eiffel’s assignment attempt right from the start (EXPR as TYPE). Strict use of this operator prevents all invalid type casts in a C# program, but this indeed requires discipline on the programmers side, since the compiler won’t stop anyone from using the ordinary conversion operators for downcasts.
I might have spotted a type in the paper on pages 14 concerning the new creation procedure for arrays of attached types: The last paragraph introduces “make_filled” but the very last code snippet on that page, which is supposed to demonstrate that creation procedure, uses the old name “make”.
I believe this should be changed from `create a.make(l, h, val)` to `create a.make_filled(l, h, val)`.
That aside, the part about the migration of ELKS was particularly interesting although I was a bit disappointed to read that, apart from the implementation of ELKS, nothing has changed. Having to leave formal argument types detachable is unfortunate but what about return values? Since the conversion attached -> detachable is always possible I see no reason to drop the “non-nullness” information. Why do _all_ publicly visible reference types have to stay detatchable (citing page 16) and not just the ones in “input position”?
You are correct that “C# provided a mechanism similar to Eiffel’s assignment attempt right from the start” but I don’t assume you mean precedence; Eiffel’s assignment attempt was introduced in 1988, over a decade before C# came into existence. As a matter of fact, the “as” construct is less like Eiffel’s assignment than like Simula 67’s QUA construct. One problem that I have with it is that I am not sure how it combines with multithreading. In an example from the C# reference at http://msdn.microsoft.com/en-us/library/cscsdfbt.aspx
string s = objArray[i] as string;
if (s != null) {Console.WriteLine(“‘” + s + “‘”); }
else Console.WriteLine(“not a string”);}
s is an attribute (field); this means, if I get it right (if not a C# expert will correct me), that another thread can interfere and wreak havoc. With object test that cannot happen.
C# digression:
Both strings and arrays are special cases in C#. Strings are immutable and arrays have (intentional, to copy a Java bug) broken covariance. In the more general case:
// hopefully the blog software won’t eat the angle brackets here….
List foos = GetFooList();
Bar b = foos[i] as Bar;
if (b != null) { UseBar(b) };
What happens if another thread changes foos[i]? b has a reference to the old value, so it can’t be collected yet. So it’s alive, but foos[i] doesn’t point at it anymore. Type safe, though probably not correct. Also, the properties of b could be changed by the other thread in a not-thread-safe-way, if the programmer wasn’t careful.
But we already knew that mutable state doesn’t play well with multithreading, right?
OK, WordPress ate the angle brackets. The first line of code above should read:
List<Foo> foos = GetFooList();
In the example quoted above from http://msdn.microsoft.com/en-us/library/cscsdfbt.aspx, ‘s’ is actually a local:
string s = objArray[i] as string;
It is not “an attribute (field)”.
But although the example is poorly chosen, your remark remains valid. Supposing that ‘s’ were a field, then yes, another thread could set ‘s’ to null. The C# code could guard against this with a ‘lock’ statement.