Seit Jahrzehnten prägt ein Begriff aus der Softwareentwicklung die Diskussion um Designentscheidungen in Programmiersprachen: der „Billion Dollar Mistake“ von Tony Hoare, dem Erfinder von Pointern und Nullreferenzen. Im Jahr 2009 entbrannte in der Entwickler-Community um die neue Programmiersprache Go eine faszinierende Diskussion darüber, ob die Entwickler mit Go denselben Fehler wiederholen – nämlich den Zusammenhang von Referenzen und Nullwerten nicht sauber zu trennen. Die Debatte offenbart grundlegende Designprinzipien, die auch heute noch aktuelle Bedeutung haben. Tony Hoare bezeichnete seine Idee der Nullreferenz als „Billion Dollar Mistake“, weil sie unzählige Programmierfehler, Sicherheitsrisiken und Systemabstürze hervorgerufen hat. Null-Pointer-Dereferenzen gehören zu den häufigsten Ursachen für Abstürze und schwer zu behebenden Bugs.
Dennoch sind nullable Pointer in fast allen klassischen Programmiersprachen Teil des Kerns und tief in deren Laufzeit und Typensystemen verankert. Dabei werden zwei ganz unterschiedliche Konzepte vermischt: das Konzept des Zeigers oder der Referenz an sich und die Möglichkeit, dass eine Referenz auf „nichts“ zeigen kann, also null sein darf. In Go, einer Sprache, die zum 2009 gerade in Entwicklung war, äußerte ein Entwickler namens Sebastian Sylvan von Anfang an die Befürchtung, dass man hier genau jene Fehlentscheidung wiederholen könnte, die Hoare beklagt hatte – nämlich die beiden orthogonalen Konzepte „Referenz“ und „Nullable“ im selben Sprachkonstrukt zu verschmelzen. Go verwendet nämlich sogenannte „Pointer“, die wie in C oder C++ auch den Wert nil annehmen können. Das heißt, es gibt keine saubere Unterscheidung zwischen einer reinen Referenz (die immer gültig ist) und einer Referenz, die auch null sein kann.
Diese Debatte wurde auf der öffentlichen Mailingliste golang-nuts heftig diskutiert. Auf der einen Seite standen Experten wie Ian Lance Taylor, die aus Sicht eines Systemprogrammierers globale Nullpointer-Checks nicht als Problem ansehen und die Pointer-Semantik von Go als bewährt und pragmatisch verteidigten. Auf der anderen Seite argumentierten die Verfechter einer orthogonalen Trennung, dass man in einer modernen Sprache die Möglichkeit haben sollte, Referenzen als entweder nullable oder non-nullable explizit zu typisieren. Diese Typen würden der Kompilierzeit erlauben, viele potentielle Fehler auszuschließen und das oft essenzielle Null-Checking nur dort zuzulassen, wo es wirklich notwendig ist. Sebastian Sylvan verwies in dieser Diskussion auf bestehende Lösungen in anderen Sprachen wie Eiffel oder Haskell, die bereits Konzepte wie Void Safety oder Maybe/Option Types implementiert hatten.
Dort werden Pointer und ihre Nullable-Eigenschaft strikt getrennt – man hat einen Typ für Nicht-Null-Referenzen, bei denen der Compiler garantiert, dass sie niemals null sind, und einen Typ für Nullable-Werte, die explizit geprüft oder behandelt werden müssen. Diese Herangehensweise vermeidet große Klassen von Laufzeitfehlern und ermöglicht es Programmierern, an der Mehrheit der Stellen ohne Nullprüfungen zu programmieren, wenn der nicht-nullbare Typ genutzt wird. Ein Kernargument der Befürworter war die Erhöhung der Sicherheit, denn es ist extrem schwierig, in großen Programmen sicherzustellen, dass hier und da nicht versehentlich ein Nullpointer dereferenziert wird. Die statische Typprüfung wäre imstande, solche Fehler bereits vor der Ausführung zu vermeiden. Gleichzeitig ist null in vielen realen Fällen unvermeidlich, beispielsweise bei Funktionen, die im Fehlerfall keinen gültigen Wert zurückgeben können oder bei Datenstrukturen wie verlinkten Listen, es sei denn man zieht aufwendige Konstrukte wie Dummy-Objekte oder Option Types heran.
Es geht daher nicht darum, Null komplett abzuschaffen, sondern es als explizite Eigenschaft einem Typ zuzuordnen. Die Gegner einer solchen strikten Trennung sahen die Probleme vor allem im praktischen Alltag: Das Programmieren würde unnötig kompliziert, da überall zwischen nullable und non-nullable Typen hin und her konvertiert werden müsste. Diese Konvertierungen müssten explizit vom Entwickler verwaltet, geprüft und ausgeführt werden, was die Einfachheit und Produktivität mindere. Zudem wiesen Skeptiker darauf hin, dass Go gerade auf Einfachheit und einen pragmatischen Umgang mit Fehlern ausgelegt sei – die Sprache verzichtet bewusst zum Beispiel auf Ausnahmen und setzt stattdessen auf multiple Rückgabewerte zur Fehlerbehandlung. Das bedeute, nullable Pointer seien kein so großes Sicherheitsproblem wie in klassischen Sprachen.
Außerdem sei es unklar, wie sich nicht-nullbare Referenzen mit dem Go-Feature des Zero-Value-Verhaltens vereinbaren ließen, bei dem Variablen automatisch mit einem automatischen Initialwert (zum Beispiel null für Pointer) initialisiert werden und keine explizite Zuweisung notwendig ist. Darüber hinaus wiesen Kritiker darauf hin, dass selbst moderne Architekturen und Garbage Collector immer noch effizient mit Null-Referenzen umgehen können und dass viele Fehler, die als Pointer-Probleme erscheinen, in Wirklichkeit aus falscher Speicherverwaltung oder unzureichendem Fehlen von Bound-Checks herrühren – also andere Ursachen haben. Dennoch bleibt die Diskussion, ob eine explizite Trennung zwischen nullable und non-nullable Referenzen ein notwendiger Schritt für zuverlässigere und robustere Software ist. Zahlreiche weitere Sprachen nutzten inzwischen Konzepte wie Option Types, Nullable Types oder entsprechende Sprachmechanismen. Microsofts F# etwa unterscheidet zwischen Referenzen mit und ohne Nullwerte, und schon in den Entwürfen von Spec# und ähnlichen Sprachen wurde viel Energie auf die Vermeidung von Nullpointer-Ausnahmen gelegt.
Die größte Herausforderung ist dabei pragmatischer Natur: Wie können solche Konzepte in eine Sprache eingeführt werden, die auf Einfachheit, Effizienz und Leistung optimiert ist? Wie geht man mit der Initialisierung von nicht-nullbaren Referenzen um, vor allem wenn sich Objekte rekursiv gegenseitig referenzieren? Wie vermeidet man Überkopflasten durch zusätzliche Prüfungen? Und wie passt das zu den bestehenden Features von Go wie dem Zero-Value-Defaults, Channel-Typen und dem Verzicht auf Ausnahmen? Ein Vorschlag, der diskutiert wurde, war eine Art statische „Null-Cast“-Anweisung. Damit kann der Compiler zur Kompilierzeit oder zumindest an bestimmten Kontrollpunkten erkennen, dass ein Pointer eindeutig nicht null ist und innerhalb eines Blocks ohne weitere Nullprüfungen verwendet werden darf. Außerhalb solcher Kontrollblöcke darf der Pointer nur als nullable betrachtet werden. Diese Idee soll eine Balance zwischen Sicherheit und Praktikabilität bieten. Die Debatte zeigte auch einen Spagat zwischen langgedienten Systemprogrammierern, die die Pragmatik wie auch den Umgang mit Null-Referenzen in C/C++ und ähnlichen Sprachen verteidigten, und moderneren Sprachdesignern, die Komfort, Fehlersicherheit und Entwicklerproduktivität in den Vordergrund stellen wollten.
Die Frage ist bis heute, ob Go an dieser Stelle einen Kompromiss eingehen wird oder einen neuen Weg findet. Abschließend lässt sich sagen, dass die „Billion Dollar Mistake“ ein grundlegendes Problem der Spracheducation in der Informatik illustriert: Das Vermischen orthogonaler Konzepte führt zu Fehlern und Sicherheitslücken, die vermeidbar wären. Programmiersprachen, die konsequent Nullable und Non-nullable Typen trennen, helfen Entwicklern, Fehler früh zu entdecken und vermeiden unnötige Null-Checks im Normalfall. Gleichzeitig sind die Transition und die Umsetzung immer ein Balanceakt zwischen Sicherheit, Einfachheit und Leistung. Go steht vor der Herausforderung, diese Designentscheidung bewusst zu treffen, um nicht denselben Fehler zu wiederholen, der schon so viele Milliarden an Kosten verursacht hat.
Die in der Community geführte Diskussion von 2009 ist daher nicht nur eine historische Fußnote, sondern weiterhin hochrelevant für aktuelle und zukünftige Sprachentwicklungen weltweit.