Language Design Deal Breakers

Standard

I’m a bit of a programming language nerd. I love to learn new languages. That said, I spend most of my days writing C++. It’s a truly awful language, with many warts and problems, but I know it well and with enough effort and pain you can get the job done. The biggest benefit of C++ is purely an historical accident: it’s great because it’s popular. There’s no trouble finding people who know it, or libraries that work with it.

The point is, that in order for me, or anyone else, to actually switch from C++ to a different language, there has to be a substantial improvement to overcome the inertia that C++ has built up over the years.

I find that when I look at a new language, there are a number of “deal breakers” which simply mean that regardless of all its other nice features I will never take it as a serious contender. Note that this isn’t a fair fight. Something can be a deal breaker even if C++ doesn’t have that feature either. Any language has to be so much better than C++ that the benefits outweigh the inertia. A deal breaker is a feature that either A) puts it at a disadvantage compared to C++ or B) puts it on equal footing with C++ in an area where C++ is doing particularly poorly.

Here’s my list of language design requirements, which if unmet would be deal breakers for me, what’s yours?

1. Must be statically typed

I know you’re supposed to be diplomatic and claim that there’s two sides to this story, and no real right answer, but really people who think dynamic typing is suitable for large scale software development are just nuts. They’ll claim it’s more flexible and general but that’s just nonsense in my opinion. It may be true compared to utter strawman examples of static typing. If you think this is an argument, go learn Haskell, and then get back to me.

There’s a wonderful correspondence between programs that can be statically typed, and programs that can be statically understood by other people. In other words, programs that you can understand without having to run a debugger or REPL, by just reading code. If you’re writing programs that rely on dynamic typing to work, you’re probably writing exceedingly difficult to understand code. If you are writing code that can be statically understood by other people, you’re probably writing down comments to indicate the allowable types and preconditions anyway (or you should) – so you might as well have those properties checked by the compiler.

Most code does not rely on dynamic typing to work. They could be written just as well in statically typed languages. In that case it’s just a matter of convenience – make specifying types light-weight enough that the extra syntactic overhead doesn’t outweigh the benefits (type inference helps, even the simple “local” kind that C# and C++ 11 has cuts down on much of the noise).

Then there’s the correctness issue. Static typing catches mistakes early. This is a good thing. Now, dynamic typing advocates will say that of course any real program will also have a unit testing suite, so types can be checked that way! This is obviously naïve in the extreme. In the real world writing unit tests for everything very frequently fails the bang-for-buck test – the cost is just too high. This cost comes in two forms, first the cost of writing the tests, and second the friction it introduces to future modifications and refactorings – after all if you have to update 20 unit tests to refactor a messy interface, you’re a lot less likely to do it. Examples of code you probably don’t want to write unit tests for includes gameplay logic which gets written and rewritten twenty times before ship as the result of iteration. Having to write test harnesses and tests for all this throwaway code is madness. Then of course there’s code that’s just plain hard to unit test, such as graphics. The point is that pervasive unit testing is a fantasy – it may be feasible in some domains, but it’s certainly not the case everywhere. Static typing is a great compromise – you basically document the things you would’ve put in comments anyway, but you do so in a standardized way that can be checked by the compiler. This catches many errors for very little cost. When unit tests makes sense (as they sometimes do), you can add those too for even more coverage, but when they don’t you still have a basic sanity check.

Finally, performance. Yes, JITers can be very clever and employ tracing and stuff to close some of the gap here, but of course those techniques could be used by a statically typed language implementation as well. If we can statically decide what methods to call and what types to use we will always be at an advantage compared to a language where this has to be determined at runtime. Not to mention that statically typed languages will encourage you to write code that can be executed efficiently (e.g. variables don’t change types over the course of a program). Also, static typing allows you to make performance guarantees that won’t be randomly broken tomorrow. For example you can store an unboxed array of values, or store a value on the stack or embedded within another object. You don’t have to rely on a runtime tracing system to uncover and apply these optimizations (with typically no way to ensure that it actually occurred), which also means you don’t have to worry that you’ll fall off a performance cliff in the future when something subtle changed which caused the JITer to fail to optimize something. The programmer is in control, and can statically enforce important performance-related choices.

Of course if you want to allow dynamic types as an option for a subset of cases where it makes sense (e.g. dealing with untyped data read from external sources), that’s fine.

2. Memory safety

C++ fails this one, but it’s still a deal breaker. If I was content with worrying about memory scribbles and random access violations I’d just stick to C++. In order to offer enough benefits to convince me to switch from C++, your language needs to be memory safe.

Now, let me also state that I think the language needs an “escape hatch” where you can tag some function or module as “unsafe” and get access to raw pointers, the point is that this needs to be something optional that you have to explicitly enable (using a compiler switch, ideally) for very isolated cases, and you should never be required to use it for typical “application level code”.

This also implies some kind of guarantees about memory reclamation…. We’ll get to that.

3. Yes, memory safety – no null pointer exceptions!

I use a broader definition of memory safety than some. In my view, if a program follows a reference and crashes because the reference is invalid, it’s not a memory safe language. This implies that in addition to making sure you never point to stale memory or memory of the wrong type (through type safety and automatic storage reclamation), you also have to make sure that a reference never points to null.

Null pointer exceptions have no business in modern languages. Getting rid of them costs no performance, and statically eliminates the one big remaining cause of runtime crashes that we still see in otherwise “modern” languages. Yes, it requires some thought (particularly w.r.t. how you do object construction), but this is a solved problem. There is only one valid reason not to do this and that’s legacy code – perhaps you didn’t know what you were doing when you started off, not realizing the issue and how simple the solution is, and now you have too much legacy code to fix it. A language without legacy issues should get this right, and yet many don’t.

Note: it’s not enough to simply have a “non-nullable-pointer” type, not even if it’s checked at compile time. You have to make sure that nullable pointers cannot be dereferenced. In other words, using the pointer-dereferencing operator on a nullable pointer should literally be a compile-time type error. You should have to do a check of the pointer first which introduces a dynamic branch where on one of the sides the type of the pointer has been changed to be non-nullable, which enables the dereferencing operator again.

Having every few statements potentially trigger a runtime crash is an extremely expensive price to pay… for nothing! We’re not talking about a pragmatic tradeoff here, where you pay the cost of reduced reliability but get something else in return – there’s just no real upside to allowing dereferencing of potentially null pointers.

4. Efficient storage reclamation

I mentioned above that I require automatic storage reclamation, well I also require it to be efficient. Too many modern languages fail this. I don’t really mind how they achieve performance, but two ways you could do it is to support pervasive use of value types (i.e. types that get embedded within their owners – which drastically reduces the number of pointers in your heap, and therefore the cost of GC), along with thread-local heaps. Concurrent collection is another solution, though you’d have to be careful not to incur too much of an overhead on the mutator with whatever memory barrier you use. I’ve written about this issue twice in the past: one two

5. Great Windows support

Windows is still the most popular OS by far. If you don’t give me installers, or easily-buildable code, for windows I’m not buying. I realize a lot of academia is used to *nix, which causes a disproportionate preference for linux and mac in projects such as this, but in the real world your customers actually use windows so you should make sure it works.

Conclusion

The most common one of these deal breakers is probably dynamic typing. I usually just close the tab immediately when I see that a language is dynamically typed. So while strictly speaking this is the most common issue, it doesn’t feel like it because I never invest any amount of time even considering those languages anymore. They may have some benefits in terms of theoretical elegance (e.g. Lisp, or even Lua or IO), but for actual work I’ve seen too much of the benefits of static typing, and too much of the horrors of not having it. It’s so obvious, that it barely qualifies as a deal breaker – it’s not even in the running to begin with.

So really, the most common deal breaker is probably point 3 above – Null pointer exceptions. Whenever I see promising languages like Go, D or Nimrod, that still repeat Hoare’s billion dollar mistake I get a bit sad and frustrated. It’s like seeing a beautiful, elegant, car that has just one tiny flaw in that the wheels occasionally fall off. It simply doesn’t matter how many awesome and innovative features you have if you don’t get the fundamentals right.

169 thoughts on “Language Design Deal Breakers

  1. This mostly makes me curious for the list of languages that do comply with all of this list! Rust is pretty much the only one I can think of, I guess Haskell also goes a long way. What other languages would do?

    • Yes, Rust is one of them. Eiffel got rid of the null pointer exceptions in a recent version (which was painful) so that might be another. Parasail seems like another candidate.

      EDIT: I should say that this is why I’m frustrated with languages and want to make my own. None of my requirements are unreasonable. It’s clearly possible to meet all these things, and yet too many languages fuck it up for no reason.

      • If you don’t mind me asking, are you really interested in making your own language that you would actually consider making it? Last year I wrote a compiler for my own language, and while it wasn’t as exciting or useful as I though it would be, it still turned out to be pretty cool and at times neat. Now that I’m back to the old C++ I’m still wondering if it would have been better to develop it further or not, and I sometimes get this urge of just getting back to making it (Although with some major design and philosophical changes).

      • I’ve started on my own languages many times, but I simply don’t have enough free time to do a good job of it. I know the main design features that are important, and the rest can clearly be worked out, but it’s still actual *work* to do it.

      • Given that, even if they aren’t many, there *are* languages which meet your stated criteria, why do you still want to write your own? What’s still missing?

      • Nothing’s really missing. I have some opinions on Rust that are fairly superficial – I’d be happy if it became solid enough to actually use for real. However, I’d be even happier if there was a few more solid competitors going after the same rough goals with slightly different approaches – maybe there’s a different approach to certain things that would work even better?

        I think I’d want more of an emphasis on light-weight concurrency (Rust tasks are way too heavy) – e.g. cilk-like tasks, and of course I have my own syntactic preferences (for example – I think they make heap allocations a bit too pretty, encouraging its use).

    • “Julia is a high-level, high-performance dynamic…” bzzzt.

      From the benchmarks it seems to at least avoid one of the common pitfalls of dynamic typing, but who knows how much of that carries over to “real world” code. This makes it interesting enough to at least take a closer look at (so I will).

      Regardless, I need my type checking to be productive.

      • It is dynamic in sense it acts like popular dynamic scripting languages, but in essesnce it is statically typed language that strongly relay on that property in its philosophy (overloaded functions world) and in speed optimizations, which are based on code path specializations (that’s why it achieve such good resuts).
        In fact it is designed to be compiled language as well, and developers wants that functionality in the future.

    • No, null-safe dereferencing isn’t quite what I’m talking about. The point is to have two different types “nullable reference” and “non-nullable reference” – only the latter supports any kind of dereferencing operations (the former can be converted to the latter through some kind of branching operator – where the “non-null branch” is only entered if the reference is actually not null).

      Null-safe dereferencing (assuming that’s the *only* kind of dereferencing you allow – which of course isn’t the case in C#) on nullable references would be better than the current state. You really shouldn’t have to guess whether or not a reference is “really there” or not, though. Most references are never supposed to be null, so you should be able to express that with a non-nullable reference.

  2. I am highly anticipating the release of Celyon as it’s union typing gives it alot of the flexablity of dynamic typing while still allowing the IDE and compiler to find errors.
    It also has a very high preformace potential ss it compiles to the JVM and JSVM which have both already proved very prefomant. http://ceylon-lang.org/

    I would also like to point out that although windows still dominates the user and business PC application markets that even outside of academia the developer, server, mobile and embedded application markets are all still dominated by *nix

    • That looks neat. It runs the risk of failing #4 – like Java does. I only skimmed it so far, but I couldn’t see any support for writing “bulky” data types where you embed members directly instead of hiding them behind references. If that’s the case you’ll probably end up paying similar costs for heap-scanning, etc. due to the incredible number of references you end up with.

  3. It sounds like you could get most (all?) of your memory feature requests implemented by sticking to well-crafted c++11 classes you make.

    • You can get far with C++, but having to rely on carefulness is sort of an issue. There’s too many really complicated and subtle aspects of C++ that have surprising and even dangerous implications. E.g. the effective c++ books, great as they are, are probably the best argument against C++ you could make. You shouldn’t *need* books like that to tease out all the incredibly intricate aspects of a language.

      However, as I said I know it well, and knowing C++ well is a good competitive advantage, so realistically doing the best you can in C++ is probably the right approach – *unless* a language emerges that’s so obviously better that it’s worth the transition.

    • You can actually trick the Clojure compiler into using static types if you try hard enough (type hints, protocols, Java interop etc.). It’s a bit ugly though…. I’m hoping Clojure 2.0 eventually tidies up all this stuff.

    • Clojure is one of those disappointing languages because it’s promising in a lot of ways (e.g. STM), and it even runs on a VM built for statically compiled languages… For example it’s great that they have an STM system that (ostensibly) prevents mis-use of transactional variables… but then they squander it by making that a runtime exception instead of a compile-time guarantee.

      I’d pick Haskell over Clojure.

      • I haven’t. That seems promising. I am a bit skeptical towards “pluggable” and “gradual” type systems. IMO static typing is at its best when it can *add* information, rather than just being a static verification pass (e.g. the way Haskell’s Map.lookup method is polymorphic over the exact strategy you want to use to handle failed lookups by looking at the types used in the *caller* code).

  4. This post just shows how deep you are encroached in a mindset, and how you reinforce your certainties with more of the same.
    There are too many sweeping declarations and false generalizations in your post for me to start debunking them one by one. I’ll be leaving a reference for you to read and (re)educate yourself.
    http://blogs.perl.org/users/ovid/2010/08/what-to-know-before-debating-type-systems.html
    Too bad you won’t consider anything different than what you already know, you’re missing out on so much. CS is full of wonders.

      • Your original post was an interesting read.

        That said, I don’t agree with your views or conclusions. Erlang is dynamically typed, should only be dismissed immediately by a fool. It’s used for many large, complex systems. I don’t want to be disrespectful, because I have no reason to be, but I’d reconsider your opinions like Daniel said. There’s some amazing (superior) stuff out there that doesn’t meet your criteria.

      • I have used Erlang, and I think Haskell is by far a better choice that provides a similar experience to Erlang, precisely because it offers more static guarantees. The remaining features of Erlang that Haskell doesn’t have don’t really rely on dynamic types much, as far as I can tell (e.g. hot-swapping code).

        Assuming I don’t know about the other stuff out there because I have come to different conclusions than you about their usefulness is a bit of a fallacy. I have used many dynamic languages and after much consideration have come to the conclusion that the dynamic typing aspect isn’t actually a net benefit – quite the opposite.

      • What do you think of Dialyzer, Erlang’s static analysis tool ?
        re: hot-swapping, I think it is made easier by dynamic types.
        Also what do you think of Typed Racket ?

  5. Kill two birds with one stone. Generalized Algebraic Data Types satisfy #1 and #3. This is easy to see in ML-inspired languages like Haskell, F#, Scala and Julia. The “Maybe T” type is defined as “Just T | Nothing” and forces you to deal with the “Nothing” case when it is possible.

    GADTs also relieve you of some of the hassles of OOP. You don’t have to rely so much on function overloading or classes having a common superclass. You could, for example, define a function for checking if any two game objects intersect “IsIntersecting((Shape | Sector | Entity) a, (Shape | Sector | Entity) b)”.

    GADTs also tend to imply a language has pattern matching, which is an awesome feature to have.

    • Unfortunately, Scala’s Option is defined as “Just T | Nothing | null”, and null does not support Option’s operations. I certainly hope that you’re willing to write “MyOption match { case Some(x) => foo(x); case _ => None}” every time you mean “MyOption map foo”

      • From what I’ve read, the Scala dogma seems to suggest you to pretend that null doesn’t exist, except when interacting with non-Scala code.

        It’s an annoying situation, but assuming your team adheres to a programming style, it does mean that you can use Options as I described them.

    • I think you are confusing ‘algebraic data types’ — which sounds an awful lot like what you described — and ‘generalized algebraic data types’ (GADTs).

      • Ah, you’re correct. I had mistaken GADTs as “the generic/templated version of ADTs”, but I see now that ADTs are actually “generic” and GADTs are much more than what I was describing.

      • ADTs aren’t necessarily allocation heavy. Boxed objects of known sizes can be allocated on the stack, and functions that accept ADTs as parameters can be specialized into multiple overloaded versions so that their input parameters don’t need to be boxed. A lot of it comes down to the compiler being smart enough, but there are still some unavoidable situations where code inadvertently causes boxing without being obvious.

        I feel an overarching issue with boxing is that compilers have no good feedback mechanism to say “I can’t compile this statement without boxing, if this code is in a hotspot, you may want to take a closer look at it”. Until such feedback exists, and without intimate knowledge of the compiler’s optimizations, we’re forced to assume the worst case.

      • I love haskell too, but I would say haskell fails #3 too, because it does allow partial functions, which can be a horror. E.g.:

        head [] = error “…
        head (x:xs) = x

        ouch, a runtime error.

      • I wouldn’t mind having to mark partial functions some way, but I think it probably fails the bang-for-buck criteria to disallow them completely. That said, in practice I see far fewer failures due to partial functions than I do due to null pointers.

      • But many functions really are partial! That is exactly what exceptions were designed to handle.

    • What about OCaml? It is obviously a ML-inspired language (I’m not sure why it’s not in your list among Haskell, F# and Scala to start with), and it is the only one of these with Haskell to support GADTs — they may also be expressible in Scala, but that is not clear nor necessarily in a convenient way.

      • FWIW I think OCaml passes most of my requirements, except the fourth one. I have a number of other issues with it (along with ML and F#), but they don’t quite qualify as deal breakers.

  6. very nice post, when knowing different languages I always hear programmers from scripting languages ( or diet ) talk about the beauty of javascript, or ruby on rails. Without even knowing what is garbage collection, or generations.

    I really like your requirements, especially the third one, would eliminate a lot of problems and a lot of code lines for defensive programming. Good job

  7. I think that your Windows Support point could be turned into a more general “easy setup instructions” and could be applied to a whole range of projects. Just because you’re using Ubuntu with apt-get doesn’t mean everyone is!

  8. I love reading bashing of dynamically typed languages on a WordPress (PHP powered) blog fueled by JS. Oh the irony.

  9. Advocates of dynamic languages claim they don’t need type safety, yet both ruby and python have made tools that analyze your program to check type errors. Wouldn’t it be cool if the compiler could do that for you every time?

    Thats also how I feel about python no brackets . Lets put one of the most important lanugage features in a symbol that is indistinguishable from several different codes, inconsistent across programs and website, and used all over with no meaning in the rest of your code. Pure madness, If it was so easy to just use white space for control structures you wouldn’t need to make tools to identify the undetectable errors..

    Both of these are big red flags that should make people think twice about language design.

    • A lot of statically typed languages have a very “dynamic feel”, even though they’re fully statically typed, due to type inference. Haskell, ML, F#, and many others. In fact, there are amusing anecdotes of talks where the audience has been “tricked” that F# is a dynamic language and liking it, only to be told afterwards that it’s actually fully statically typed and will catch many errors at compile time.

      • I’m not sure…asked in #ada and got the following reply: “http://en.wikibooks.org/wiki/Ada_Programming/Types/access#Null_exclusions should explain how to avoid it. I also thought referring a null would raise an exception, not just crash (unless you don’t deal with the exception). null pointers would still be easier to deal with then pointers”

        What do you think about it?

      • Yeah, it’s about the same as Spec# or a custom C++ 11 library. I.e. it provides some facilities for helping you avoid it, but it doesn’t actually eliminate it by design. The point is that an Ada program can still randomly crash on just about any statement if you dereference a null pointer, whereas other language will eliminate that at compile time.

        Saying that an NPE isn’t a crash because you can catch the exception is kind of a silly cop-out, IMO. An unexpected exception is a crash, because you can’t really do anything to fix it.

      • I think that the analysis to prove the bounds is pretty simple, and would make it free in most cases. In the others, the cost of either bounds checking or bigints doesn’t need to be that high; and could be easily optimized away by the programmer by specifying “int64” where known safe.

  10. Great post. I’m impressed that you have these views yet can still write C++. The second to last last program I wrote in C++ only printed the message “segmentation fault”. The last one swapped itself for the JVM and ran like a charm.

    I fully agree with static typing. I was drifting towards the dynamic camp until I discovered the Haskell-heavy Scala. I blogged my love for the type system if you’re interested. http://proseand.co.nz/2013/05/04/maybe-type-checking-is-a-good-idea/

    Nulls REALLY bother me too. I actually have started a blog post on why Option types are far superior. Unfortunately for Scala., it runs on the JVM and interacts with Java so it’ll never be null reference free as you pointed out.

    +1 for windows support. I hate windows, but I’ve yet to work somewhere that didn’t expect developers to use it.

    • Thanks, I had a similar conversion experience years and years ago.

      I get that short scripts “aren’t so bad” in dynamic languages, but “aren’t so bad” is not a great argument in favor of anything. I find that even for short little scripts I make fewer errors in Haskell than in Perl or Python or whatever. It only takes one experience of running a quick script for twenty minutes and then have it crash on some corner case you missed that would’ve trivially been flagged by a static type system, to realize that even throwaway scripts need static type checking.

      I briefly touched on it in the blog post, but if all you’re doing is dealing with external, untyped data, then maybe a dynamic language that allows you to use pretty syntax could be preferable (e.g. just use field access to process a JSON document, and have it parse “on the fly”). But those scenarios are fairly rare that it doesn’t make sense to throw out the entirety of static typing to slightly improve this one case (after all – in e.g. Haskell you’d just use a parser combinator library, which is only slightly more effort but gives you all the benefits of Haskell for the actual processing).

  11. Check out Elm. http://elm-lang.org/

    You don’t seem to be much of a web programmer, but I would look it up anyways. It’s a purely functional & reactive programming language with syntax like Haskell.

    • I have looked at it some, and I’ve looked at FRP in general before. It’s on my list of things to really spend some time with because I think it has the potential to be really cool, but I get the impression that you really need to spend a lot of time with it before it “clicks” (my brief skimming left me feeling slightly confused).

  12. “If you’re writing programs that rely on dynamic typing to work, you’re probably writing exceedingly difficult to understand code”

    That’s a very weird statement imho. You sound like you’ve never really tried a language like Python – which is by far much more simple to read & understand than C++. You don’t need to be a statically typed language to be simple to understand. In fact, it’s *harder* to write something that’s difficult to understand in Python than in C++ 🙂

    • I have tried plenty of dynamic languages, including Python. You’re missing the point. The high level features that make Python more readable than C++ generally have nothing to do with dynamic typing. There are plenty of high level statically typed languages.

      By “programs that rely on dynamic typing to work”, I’m talking about writing code where variables randomly change type, or where methods get patched up at runtime to do different things at different times, or where the type of a variable (or rather the value in it) depends on control flow. This kind of stuff is a nightmare to understand for anyone but the person who wrote it (or the person who wrote it one year later). Even if you are using Python, you should avoid doing that sort of stuff anyway. Losing the ability to do this stuff in a static language (or rather, making it a bit more explicit/harder – e.g. making you list all the options and handle them explicitly), is not a loss – it’s a gain.

      • Just use what you want to use. No need to try to convince people, they have to come to their own conclusions. I personally disagree. Dynamic languages are here to stay regardless of anyone’s thoughts or feelings. The web is powered by them including your blog.
        1st step in practicing what you preach should be finding a static typed alternative to the PHP based, and of course ridding yourself of that Javascript. 🙂

      • The world would be a very boring place (and stuck in the dark ages) if nobody ever tried to convince each other about better ways of doing things.

        IMO dynamic languages have had their heyday. They made sense in the 90s when it looked as if worrying about performance was going away. That’s changing now. With mobile device battery life, and data center power usage, becoming the main factor to optimize for, runtime performance will once again be a significant issue. If you’re running your website in a cloud service where you pay for CPU utilization, a 2x performance hit will roughly correspond to a 2x bank account hit. On a mobile device it will translate directly to battery life.

        I really do think that dynamic languages are an anomaly – a blip in history – that we’ll eventually move away from. They came about from a desire to use higher level abstractions, in a time when performance was deemed unimportant. But then we discovered how to get the same high level abstractions in statically typed languages, without the cost to safety, productivity and performance, and the world changed to once again value performance as a first order concern, while application complexity and scale has grown to the point where we really need better approaches to programming-in-the-large than dynamic languages offer.

      • Right, but unless I’m misreading the documentation, Kotlin *does* provide static guarantees about nullable values coming from non-Kotlin code. As long as *your* code is written in Kotlin, you can be assured that *you* haven’t produced any NPE’s.

        Of course, libraries you use might still blow up, but using external libs is a trust exercise in any language. 😉

  13. I totally agree that compiler verifiable languages are always preferable. But you cant go by such a simple list because of how the total feature set interacts. As an examole, Erlang doesnt have ‘static’ types. Yet, variables are only assignable exactly once, so they will never have conflicting values reassigned over their lifetimes. If you pattern match over inputs (with type being a possible match condition), you effectively are declaring the type. Because of erlang pattern match and guards, you actually have stronger guarantees about the input; than knowing that you simply have a string, etc. Even though java has static types, it has the problem that you cant return multiple value or tuple types; which means temporary classes all over the place to simply glue a return value and an some out of band state, etc. nobody consistently documents the state that an object must be in in the type sig. (Not null, an open for read file rather any file oblect, etc), or the legal calling orders. You can only emulate this with static types to a limited degree. Sing# is an interesting example here. I would suggest that enforcing calling orders (messaging patterns) for the api is at least as important as type declarations alone.

    When concurrency is involved, all the privacy you thought you had in an object is exposed as fake because it is usually executing the object in the caller’s thread, creating all kinds of concurrency hazards, rather than in a “thread” internal to the object (ie: erlang consuming a message from its queue). Dont even get started on the default of mutability/locking everywhere.

    I think Go’s static duck typing plus messaging over channels is a great start. The non-null type issue that almost all languages , but….

    I cant see how anyone with this list can use C++ though…. :-). Memory unsafety is so much worse than a lack of static types from perspectives such as security. At least these languages that you wont consider will reliably throw an exception on a garbage deref. Anything you think the compiler proved correct in C is easily undone by an errant buffer overflow.

    • Well this is a list of deal-breakers. A language could pass all requirements and have other issues, or lack other important features (concurrency/parallelism features in particular, is something I look for).

      I use C++ because of legacy, and because it’s the only real option for high performance, reasonably-high level code, w.r.t. tooling, availability of man power and plain executable performance.

      I may call this list “deal breakers”, but they’re still only about my preference when I have do have a choice… except for #4, that one will actually make it literally impossible to use a language. I can’t write a high performance game in Haskell or ML (or Java, or C#) – they’re just too slow, and I suspect much of it is an unavoidable consequence of the language design.

  14. I’m not sure I get most of your post, but why not simply not allow null pointers at all? Also I’m not sure I interpreted this correctly: “They’ll claim it’s more flexible and general but that’s just nonsense in my opinion. It may be true compared to utter strawman examples of static typing. If you think this is an argument, go learn Haskell, and then get back to me.” – could you clarify what you mean?

    • Well you’re going to need some kind of “optional value”. You could have a separate nullable pointer type, or have some kind of discriminated union type (a la Haskell) – I don’t really care either way, so long as you don’t allow dereferencing of a nullable pointer.

      The flexibility point is about how a lot of DT enthusiasts simply haven’t used high level statically typed languages. They think Python is productive and C isn’t, so they therefore conclude that this is all due to DT. Haskell will teach them otherwise. More specifically, the theoretical flexibility of DT – while sometimes necessary in languages that make heavy use of it (e.g. they sometimes require runtime patching of method tables to implement features like subtyping) – simply aren’t required in well-crafted static languages. Other uses of this flexibility (such as “monkey patching”) are actually a liability rather than a benefit, that have a high cost to readability and should be avoided.

      All that combined with the fact that they *lose* the flexibility offered by static typing (types carry information – they can be used to disambiguate and pick implementations based on context, for example letting the user of a library decide on error handling strategy while the writer of the library doesn’t have to care – meaning you end up writing less code), actually ends up meaning that DT IMO actually have far less flexibility and generality in practice.

      • As someone who has used Haskell and Python I have to disagree that Haskell is anywhere near as productive. Although I would mainly lay the blame lack of mutability / monadic I/O for why Haskell code is very hard to write (and slow for me at least), the type system can be very hard to satisfy. I think it is much easier to write code that wouldn’t necessarily typecheck, run it and fix any issues that come up, rather than unravel inscrutable typecheck errors. Python, for all it’s glaring flaws, is still the most ridiculously productive language for me.

      • There’s a learning curve with Haskell, sure, but once you’ve grokked it I think it beats Python by miles and miles. Once you’re no longer a beginner it’s very rare to see type errors that aren’t actually the result of a mistake you’ve made – so why wouldn’t you want to get it pinpointed and called out early?

  15. Great article!!
    Every point you make in your article is what we aim at in our language Crystal:
    https://github.com/manastech/crystal
    1. It’s a statically typed language (where you rarely specify the types, but we will allow you to enforce types at compile time)
2. We have Pointer type and we plan to mark regions of code as “unsafe” to be able to use it (useful for performance and for interfacing with C)
3. No null-pointer exceptions: if you invoke a method on something that can be null you get a compile-time error. If you try the code right now you will see it’s not like that, because we made Nil implement “method_missing” for now, so that a null pointer reference actually finished the program with a message (understand the language is still in it’s baby stage), but in the future you will have to check for non-null (but we still don’t know if that will be too annoying for the programmer, because the code will be full of those checks when you sometimes are sure that the reference is not null)
4. We also plan on using value types (embedding objects inside other objects)
5. Ouch, nothing planned on this side yet, but it’s true that we must have Windows support.
    I hope we soon get to the point where you can actually switch from C++ to Crystal without loosing anything in the way…

    • Thanks, will take a look!

      In important point about null-safety is to make sure you offer enough syntactic sugar for things like object construction so that you never need to create nullable pointers for things that aren’t actually nullable just to “work around the language”. On the construction point, I think supporting object literals as the only construction mechanism is the way to go (sort like struct initialization in C, but more general). That way objects always get constructed “atomically” – i.e. you can’t call a method from inside a constructor on a partially constructed object, etc. To support more complicated “constructors”, you’d just write a function that returns an object, this function would build up pieces of the object as local variables, and then finally copy it into the object inside the object literal that it ends up returning (optimize to eliminate copies!).

      The broader point is that if a programmer really is sure the reference is not null, then the type shouldn’t be nullable. Any time you end up in that situation, it is a failing of the language (this *will* happen – but you should strive to reduce those cases significantly by working hard to support idioms that can avoid it).

  16. Thanks for this great post!

    Incidentally, I have been recently writing some specs of the language I would love to program with, and I came to the same kind of minimum requirement list. Though, I have also in addition:

    * Meta-programming, by trying to solve the whole c/c++ preprocessor, template, generics, macros mess, and as a general rules, having a pluggable parser/compiler infrastructure (of course still by leveraging on project like LLVM to get the laborious part of generating efficient native code)
    * Compile to statically native code (that would run on desktop or console/mobile/tablets – with executable exec page restriction to take into account)
    * SIMD types/instructions seamlessly integrated
    * GPGPU integration into the language – or at least a way to provide an smooth integration through meta-programming

    The great thing is that I have been digging into some different legacy languages – dynamic and statically typed, with GC or not, JIT or not, imperative and/or functional, CPU or GPU, some of them I have been using in the past, some of them completely new to me while effectively digging into this requirement list… And It’s cool to learn from here some languages I was absolutely not aware of (e.g. Kotlin, Crystal).

    Like you, I wish that my tiny spare time will allow these specs to jump into some effective implems… time will say!

    • The lack of potential SIMD awareness (at least making it structurally obvious to the compiler that there are no loop carried dependencies in loop bodies, etc) is one thing that I wish that Go had included. CUDA is far too specialized, OpenCL not simple enough, and extensions in C++/C have abstraction leaks so huge that compilers will still fail to make obvious optimizations.

  17. The larger the project I work on, the more my requirements of a language will be similar to yours. In particular, I agree that a static type system like Haskell can give you the best of both worlds, it isn’t static typing’s fault that languages like Java exist.

    That said, I recently contributed to the entropy of the world by creating yet another dynamically typed language (to be used together with a C++ game engine), and spent quite a while using it.

    From that I have one observation I don’t see mentioned a lot: type errors are not the problem. I did an experiment whereby I tracked whenever I found a bug in programming (in the DT language) what the root cause was. Less than 5% were things that a statically typed language would have caught at compile time, but more importantly, these bugs were the quickest to find (almost always resulted in a dynamic type error immediately). The majority, and harder to find bugs were all “logic” errors that would have happened in most any language (oops, did I just admit to being a crappy programmer? 🙂 I know, anecdotal evidence, but I think it is a perspective worth keeping in mind.

    That said, the reason for creating a DT language is mostly lazyness, since to make a static language that can compete with a DT one for expressiveness you’re looking at a compiler that’s an order or magnitude more complex, and I don’t have that time right now. I’d still want a static language ideally.

    While not perfect, languages like Rust are pointing in the right direction from a memory point of view. If only someone would combine the semantics of it with less noisy syntax and better type inference a la Haskell (and a C++ grade optimizing compiler pretty please), I think we’d have a contender. I welcome the day I can realistically write a high performance game engine in something other than C++.

    • I think the value of static typing is greatly increased the less imperative your language is. Types really only catch errors in expressions (and assignments, I guess). So if you program in a style where most of the logic comes in the form of expressions, you’ll get “more coverage” from the type system, whereas if you program in a way where most of the logic comes in the form off sequencing mutations to shared state, you get almost no coverage at all (basic scoping is about the only thing that helps you catch sequencing errors – but IME it’s a pretty weak tool for that).

      Re: the frequency of errors, in the twitter discussion about this blog post John Carmack mentioned that a full 90% of all crashes at id were related to access violations, most of which caused by NULL: https://twitter.com/ID_AA_Carmack/status/338824268165025792

      That’s consistent with my experience as well.

      I’ll also point out that even the fastest dynamic language still takes *some* time to actually execute the code you just wrote. Even when it’s a standalone function that can be called directly it takes some amount of time to actually run the code (although often it requires a lot of setup, and even user input, to get the program into a state where the function you just wrote can be tested in its “natural habitat”).

      So even if all the errors were always caught on the very first try (unlikely, many code paths only run with certain combinations of inputs), you’re *still* going to be several times slower than a statically typed language where the IDE can just put a red squiggly line under a type error or null dereference *as you write it*. Now consider the scenarios where you have to (re-)load a level to set up your test, or actually play the game a little bit, or write some scaffolding… It’s easy to see that catching errors at runtime will be significantly slower than doing so at compile time, even if you luck out and the error happens right away.

      • Having a non-null type qualifier will only move the problem unless the APIs in use are designed differently (ie: a list of items where an empty item just isn’t in the list). In a language like Java, throwing an exception in an orderly way is exactly what you want. You don’t want to keep on computing when your data is junk. So as a compromise, we have a pattern throughout our code where we have things like SeverityCode.NA, where we don’t pass around null values, but we pass around values to denote that code.isEmpty()==true. This means that we treat it like any other value for most purposes; except when we *should* throw an exception where it is an empty value. I can’t stress enough that this is not a ‘null’ dereference in the same sense as C, etc; because it’s orderly and throws an exception.

        The issue is deeper. Value types where there is no real null value doesn’t fix the problem, because you now have empty values instead. This isn’t any different from having function params where we need a *positive* integer rather than *any* kind of integer: ie: “PrimeNumber doComputation(PositiveNumber x)”. For instance, you might like to be able to declare a function that takes an “enum” of all positive numbers, or wrap it in a class to give it a type; as if you are using types to construct proofs. You would have a PrimeNumber type come out of a function, so that functions that require a PrimeNumber input won’t accept any number that didn’t come from a function that (supposedly) only returns PrimeNumber, etc.

      • Yes, having non-nulls moves the problem.. It moves the problem *to its source*. This is far superior.

        The type system tells you immediately when a potentially null value has snuck into your code (e.g. from doing a lookup in a hash map that may return failure, or whatever), and you handle it right then and there. The vast majority of your pointers end up being non-null this way (probably 80% or more, IME).

        Throwing an exception is an awful solution IMO. It basically just lets the null flow through your program unchecked until finally some unrelated code ends up crashing. It should be caught immediately when the null shows up, and obviously catching it at compile time is superior to runtime.

        You definitely shouldn’t have “empty values”. That’s a terrible idea, no better than nulls. What you should do is wrap those values behind a nullable pointer (or an Option type, or Maybe type, if you’re familiar with ML/Haskell). That way you can’t do anything with them without checking if the value exists – avoiding any runtime crashes entirely.

        Yes, there are other restrictions on values that we could potentially want to track, but there’s a few differences here.
        1. References are *everywhere*. Making each one potentially invalid has a massive impact
        2. References actually have nothing to do with being “optional”. References deal with referencing things, but “optional value” is a completely orthogonal concept (that you’d want to use for non-references too).

        Why *should* references be allowed to be null? It’s like saying ints should be allowed to contain strings. The fact that you sometimes want even stronger restrictions on ints is no reason to just let anything be treated as an int. Null has no business in references, and strings have no business in ints. We could have even narrower types (with checked ranges or whatever), but there’s a point of diminishing returns, I think.

    • I don’t think 1 is true. Most of my coding happens in a “write code -> find bugs -> fix bug” loop, I just want the “find bugs” step in that loop to be as fast as possible to improve my productivity.

      If I can catch the vast majority of trivial mistakes by simply running the type checker (which the IDE can do as I type), instead of having to actually run the code, I’m going to be *much* more productive in fixing those mistakes. Yes, there will still be runtime bugs (which are more expensive to find), but at least I don’t have to debug a runtime crash to find something that the simplest of type checkers could’ve found for me at 1/10th the cost.

      • Imo the only point in the “disadvantage” of static typing that is actually valid in that lisst is #3 – it takes longer to learn. That investment pays off pretty quickly, though, IMO.

        It doesn’t actually limit expressiveness in practice. The theoretical flexibility I dynamic languages is avoided by sensible programmers (e.g. redefining methods randomly, changing types randomly, etc.), because it’s error prone, hard to maintain, and unreadable. Plus, static typing actually adds flexibility you can’t get in dynamic languages (because types carry information – I gave the example earlier of abstracting over error handling, letting the type system insert the correct behavior when an error occurs, based on what the caller of your library wants to use). So the extremely dubious value of the theoretical flexibility of dynamic languages (which does exist), combined with the fact that static typing provides added flexibility of their own, means that this point is at best a tie for DT languages.

        A good static language makes development go faster, because no matter how fast your interpreter/JITer is, a REPL still has to evaluate code to catch errors, whereas a type checker can run in the background and point out errors in the IDE as you type (or if you run it as a batch process, running type checking on a single file is effectively instant in most modern languages). This means many of the trivial errors can be caught, and pinpointed exactly, in orders of magnitude less time than a dynamic language. In addition to this static checking, many of them have REPLs too – e.g. Haskell, F#, etc. so you get the best of both.

        4 and 5 are complete non-sequiturs, and 6 just isn’t all that true, and to the extent that there is you don’t actually lose much because the language will provide other mechanisms (e.g. you don’t have to manually patch up the method table at runtime to implement inheritance, the language provides a mechanism to do it).

        Of course, using Java as the stand-in for modern languages is deeply flawed. This error is repeated all the time in this debate. Dynamic typing proponents look at shitty statically typed languages, and high level features of their favorite dynamically typed language (e.g. built in syntax and other goodies for common forms of data processing, regular expression etc.) and conclude that dynamic typing is better. They completely miss that most of the features that make Python and Ruby productive have absolutely nothing to do with dynamic typing. If you insist on using Java – then at least us MEL or PHP or something equally horrible on the other side of the comparison.

        I would never pick Java over Perl for a task that’s mostly about reading tons of data with simple processing, for example. But I probably would pick Haskell over both.

  18. Haxe WRT #3:
    class Test {
    var x: Int;

    static function test(t: Test) {
    t.x;
    }

    static function main() {
    Test.test(null);
    }
    }

    Called from ? line 1
    Called from Test.hx line 9
    Called from Test.hx line 5
    Uncaught exception – Invalid field access : x

    I still like the language though. So it’s obviously not a deal breaker for me as I also like Go 😉

  19. A non-null value is just a special case of range-checked values, so adding not-null should probably tie in with a more general range-checking/contract mechanism. Just like you can’t expect the compiler to always detect a bug in a function with this signature: PrimeNumber doComputation(PositiveEvenNumber x), You replace runtime checked attempts to plugin null values (throw exception and run to closest fault barrier), with if/else branches that just hide the bug. Your program should not completely die when null pointers happen if you have well-defined fault barriers that were put in place to handle bad input and code bugs that are not dire enough to stop running.

    If you have a nullable foo that you need to plug into a non-nullable function, then you have an if/else branch. You might have a bug if you miss the opportunity to throw an exception when a nullable foo happens (and your non-nullable function is quietly not called).

    This is why in Erlang they say “don’t do defensive programming”…. you do a “Positivist” match on what the specification says, and everything not explicitly stated is implicitly disallowed, so that all patterns that don’t match will throw exceptions, log the problem, and retreat to the fault barrier.

    • Only in the sense that ints and strings being different types are also a special case of range-checked values.

      I take the view that “optional value” and “pointer to value” are two separate and orthogonal concepts (just like ints and strings) and therefore don’t belong in the same type. You can have an optional pointer (or an optional string, an optional int, etc.), but not every pointer needs to be optional.

      Why would an if-else branch hide the bug? At least you’re being forced to actually consider the null case up front, near its source. That far more likely to avoid the problem by construction than if you let nulls infest your program until something crashes (much later, and in a place far from where the null was created).

      I agree that you shouldn’t do defensive programming – catch the bug early by immediately failing completely as soon as an invalid program state is detected. I do think that this should be caught at compile time whenever possible, and in this case it’s both possible and relatively straightforward, and we have loads of languages that have successfully done it.

  20. Yeah, so the good part is that the non-nullable type does prevent a non-null value from ever making it into the function (or getting assigned to a non-null return values); so most languages should have this (or a more general contract mechanism). I’m just noting that at the point where you try to cast a value to non-null to pass it in, it really-really should throw an exception rather than enter the function (because of the typecast to non-null – not because of a null de-ref). The only case in which you don’t throw an exception is where there is some explicitly defined handling for the case where you don’t have the required value to invoke that function. Now that you have the if-else, you know that you will see people handling the if case, and not handling the else case.

    • I’d prefer a compile time error over an exception, personally. I.e. that assignment is a type error because optional<T*> is different from T*, so you have to use the explicitly defined language handling to get the T* out of the optional<T*>.

      However, exception at the point where the nullable gets assigned to a non-nullable is better than everything being nullable, I suppose.

  21. Yeah, it’d be awesome if you always got a compile time error. This impossible however. It is just like how you can cast to a superclass unconditionally, but it depends on which instance you have when you try to cast to a derived class. If you have a notNull Foo, you can always cast it to a nullable Foo. If you have a function that *might* return null, there will be cases where you know that it the function can’t return null (ie: based on the input), and cases where you just think it can’t return null. So, you essentially have to do a ‘typecast’ to the notNull Foo in order to make use of the function.

    In any case, if you think it can never return null, then you should write your code exactly that way. If that assumption turns out to be wrong, then you get an exception that causes that thread to retreat to the nearest fault barrier and log the error. Explicitly checking for null (which you believed to be impossible) is defensive programming. The problem with defensive programming is that it is impossible to enumerate all that is illegal. Instead you write your code so that most illegal things are inexpressible at compile time, and if that’s not possible (and it will be in a huge number of cases), then force all assumptions violations to throw exceptions.

    If you want tighter compiler guarantees, then you need to go way beyond mere types. You need to be able to annotate the state that an incoming or outgoing value must be in, and to limit the legal subset set of values (of our type) they can take; which can indirectly define the legal calling orders for methods in a set of objects, etc.

    • Yes, there are cases where you get a “nullable” pointer where you know that because of the specific dynamic circumstances it actually can’t be null, so there needs to be a language provided-cast. This is basically why the language needs careful design to support this feature – there would be a lot of “accidental” nullable pointers if you just bolted it onto C# or something. You need to carefully design the construction mechanisms etc. so that most of the time you don’t have to make a pointer nullable just to work around the language.

      As for the language-provided cast, a simple “if (p != null)” type approach where the pointer changes type to non-nullable in the “then” branch (and retains its nullable type in the else branch) would do the trick. The key is that you have to explicitly handle the “null”-case. If you know it’s impossible, feel free to throw an assert(0) in the else branch. Most of the times you probably do want to do something different in the else branch. I don’t think it’s very common to have a nullable pointer that can never actually be null – I base this on the fact that it’s very rare to see “fromJust” used in Haskell code (which is the “cast to non-null, crash if not” function).

  22. And obviously… you want any dereferencing of members in an object to be only possible on a reference known to be not null. That forces the exception to happen at the right place (where you began your assumption that the value is not null), rather that at some random spot where a deref occurs.

  23. “….
    Saying that an NPE isn’t a crash because you can catch the exception is kind of a silly cop-out, IMO. An unexpected exception is a crash, because you can’t really do anything to fix it.”

    Ada make it extremely difficult to work with any types of pointers (you have to include a special library and jump through all sorts of hoops)

    Pointers are also necessary, though, for any language that wants to be taken seriously nowadays, because nothing really can be done unless you have to ability to import C and C++ functions and data types. The only possible solution is to have as many checks as possible and make the pointers hard to use by forcing the programmer be as explicit as possible.

  24. That is totally not true. Managed language exceptions are very reliable. I have worked on a Java app server where we have never had a server actually come all the way down and stop running in production (other than MySQL finally losing its mind and forcing everything to restart, which isn’t in a managed language)… over many years and across a whole bunch of servers …under heavy load… in an environment that is primarily parsing malicious input(!).

    If you are parsing input that is malicious every time you go to parse such a record, you are at an iteration of the fault barrier. We parse everything into objects (ie: no raw strings or ints anywhere), such that it always throws some kind of exception when invalid. Sometimes it would be a null pointer exception. So a “NPE” here is nothing like a disaster, and the server keeps on running just fine. At this fault barrier, it isn’t necessarily caused by a code bug. At other fault barriers, it would be a bug.

    NPEs in a managed language are not a special hazard like they are in languages with pointer arithmetic. There is an enormous difference when array out of bounds is pushing an int and an array on the stack and excepting when the index is out of bounds, versus just computing wrong addresses; and just de-referencing a pointer and relying on virtual memory tricks to cause it to reliably throw an exception. In a managed language, if you could put an arbitrary non-null value into a pointer, it would also throw an exception reliably if it isn’t pointing to the correct type.

    In any case, we aren’t in C++ where exceptions are not quite baked, and where there are other pointer arithmetic hazards that will undo other safety guarantees you thought you had in place.

    • When an exception represents a code bug – i.e. it’s not something you expected, and have written code to handle, you can’t expect to be able to cleanly recover.

      Who knows where the exception was even thrown and what you need to “undo” to get back to a clean state? If you’re lucky you can tear down the entire state of the module and hope that some master supervisor type thing can restart it, but that’s only rarely the case.

      Yes, you can carefully decide to ignore NPEs in some very limited case because you know it represents a particular kind of recoverable error (e.g. malformed input), but a random NPE in code that isn’t supposed to see *any* NPEs indicates a bug. Your program has violated its invariants (in unknown ways that eventually led to an NPE) and is no longer operating in a valid state. You could be leaking user credit card numbers on to the public internet for all you know at that point. You might as well have a corrupted heap at that point, because your assumptions about what the program will do are just as valid as if you had. Crashing is the only safe and sane way to proceed.

  25. But this is no different from any other exception that gets thrown because a bug was found. The fault barrier is the one place to catch and log these (so that you aren’t catching and logging at random locations throughout the app). You should only handle exceptions explicitly where you have a specific plan for that exception (ie: ignoring bad input, or re-try something with a backup strategy).

    So sure, ideally, you have NotNull refs as the *default*, the compiler will fail if you try to de-reference fields and members on Nullable types. This means that Nullable refs can only be passed around. An exception has to be thrown at the point where you cast the Nullable ref to NotNull so that it can be used if the ref happens to be null. You don’t want to use an if in that case, because again – you should throw an exception (implicitly, without having to enumerate the null case explicitly in code) when your assumptions are violated. So in a language that worked like this, there would be no such thing as a NullPointerException. But you would still have NullToNotNullCastException; which is perfect because … For a method call, that causes the exception to be thrown just before the function is invoked, rather than at some implementation-dependent location deep inside the function where it may be half-way through doing what it was doing.

    Think of a retreat to the fault barrier as usually being like a transaction rollback. You want the exceptions to happen in the course of preparing the data to do something permanent with it. You generally log the error and move on. If you half-way persisted something, then you need some undo logic in the fault barrier.

    This is basically where design by contract leads. If you declare that a function is not callable given that a member variable is null, then it should throw an exception if you invoke it even with an empty implementation that doesn’t actually de-reference it.

    • In principle it’s no different from other exceptions (any time your program ends up in an unexpected state, you really should crash to avoid doing more damage – this also has the side benefit of making sure the bug is found promptly instead of hiding it in a log somewhere).

      There are two big differences in practice though: NPEs are extremely frequent, and they can be tracked and “caught” at compile time.

      I don’t think the nullable “cast” needs or ought to have any exceptions. It should be an explicit check where you have to explicitly do something with the null case (which may be aborting the program if you indeed never expect that to happen). If it’s a simple cast with exception then you’re not really forcing anyone to think about what they’re doing, because exceptions can easily be ignored (and indeed, are infamous for encouraging just that behavior). It causes the crash to happen earlier, but it’s not as good as not giving you a way to silently ignore the possibility of null.

      • In some cases, you will have a fault barrier to catch what the more mundane fault barriers above it missed, an that barrier will log the error and do a system.exit with a code that says “restart me”, or “dont restsrt me like you usually would” (bash scrip wrapper, etc). That is sometimes necessary because of the existence of static variables in the vm. In a managed language, corrupting the vm is generally hard to do with even the worst code; but i will grant that static variables are trouble in some cases.

        But if you run an app with service level agreements, those logs are monitored, and any exception that escapes to certain fault barriers will set off pagers and wake people up at 3:00am. (Ie: a team distributed across all timezones).

        If you dont have nullables in some form, you will surely end up making a special nil value to compensate. When that happens, you start missing subtle violations of your assumptions; because you are trying to enumerate all the ways you could be wrong. Ie: You should not dup range checks everywhere, but construct an object with the range data; knowing that an invalid range is not constructible due to exceptions thrown during checks in construction.

        If there were no genuine issues with having non-nullable references as the default; then no modern language would be using them. Modern VMs are designed to stand up to malicious code from unknown origins, let alone code that is just buggy.

        If you made non-null the default in an existing language, it would probably be worth the effort if the compiler could correcty insert casts for code in migration.

  26. “If there were no genuine issues with having non-nullable references as the default; then no modern language would be using them”

    I’ve seen interviews with the designers of C# (though I can’t seem to find them right now – probably on Channel9) where they regret having nullable as the default. And in fact, the inventor of null, Tony Hoare, called it his “billion dollar mistake”.

    People fuck up, even language designers. Often, they only realize once it’s too late to fix.

  27. I agree totally. 🙂 Non-Nullable should be the default in modern languages. But Nullables still have to exist (under some new restrictions); just to pass around optional references. It is a mistake to let the de-refs happen at random locations throughout the code though.

    Range-checked references/values need to exist; because a tighter method signature is always going to be better. Other reference annotations are necessary to handle concurrency: (deep) immutable ref, unique ref, and mutable ref. (There is a Microsoft Research paper on this topic).

  28. I don’t see how dereferencing a null pointer is worse than dereferencing a pointer to some other wrong memory address, other than null is easier to detect.

    • Once you have type safety and automatic storage reclamation, pointers to garbage are gone, and so are pointers to incorrectly typed data. Null is the only remaining invalid reference value, and they can be statically eliminated.

      Now, you can of course make a reference point to the wrong object, but then that’s true for any variable (including non-references), so there really isn’t much you can do about those (and IME nulls are more common, since they’re used as an “exceptional” value and thus seep into your program when you don’t expect it, whereas other incorrect references require a more explicit carelessness that tends to be more rare).

  29. How is dereferencing a null pointer worse than dereferencing some other wrong address? At least null has the virtue of being easy to detect.

  30. The video about C# designer regretting the absence of non-nullable reference types is called “Anders Hejlsberg: Questions and Answers” (at 1h00m35s).

    > I should say that this is why I’m frustrated with languages and want to make my own. None of my requirements are unreasonable. It’s clearly possible to meet all these things, and yet too many languages fuck it up for no reason.

    This is true, sad and true. Every year they make new languages with all kinds of newfangled features, but what we need is just a language that does nothing new but gets all the basics right.

    How about this list of additional deal breakers?

    * Non-local error handling, i.e. exceptions. I was horrified to see the Go people argue vehemently against them. Then it looks like they panicked and changed their mind.

    * Generics. Go also fails this. A language where I can’t create a generic collection? Really?

    * (At least local) Type inference. No more “VeryLongType v = new VeryLongType()” please.

    * A syntax for declaring variables/parameters that can’t be reassigned (à la ‘val’ in Scala). It augments the readability of the code tremendously! Most of my variables are single-assignment nowadays, so it would be a shame if the compiler couldn’t enforce it. And it costs nothing!

    * Lambdas/closures. Can’t live without them anymore. But then there is the question of how to implement them, and how to enable the programmer to optimize them.

    • Yeah, I guess Generics was such an obvious thing to me that I almost forgot Go doesn’t do them. On the exceptions thing… I’m not sure what a better alternative is – e.g. do you really want to trap each division to catch div-by-zero? Probably not… Maybe something like rust where you organize your code with lots of little threads that get completely unwound and killed on an exception. I.e. you have non-local error handling, but you don’t have to deal with partially unwound state and trying to recover (which doesn’t really work in practice), once something breaks the whole thread gets torn down, and all the resources it has allocated get destroyed (you can then restart the whole thread as the only form of recovery).

  31. The only alternative to exceptions for non-local error handling that I know of is Lisp’s ‘conditions’.
    http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html

    I don’t really understand what the little threads buy you as a boundary for recovery, compared to a catch block anywhere in the call stack. If the code is exception-safe, recovery can work well at any level of unwinding. I found that writing exception-safe code is not so difficult, and it is an habit that once formed will increase code quality in other ways: easier debugging, code becomes easier to understand due to less side-effects… Sometimes you pay extra cost for copying though, but it is often worth it.

    • A catch block anywhere in the call stack still has to deal with recovery. Yes, there’s some unwinding, but who knows what state the heap is in, and what you need to do to “roll back” modifications to it? Maybe if you used transactional memory you could actually safely roll back mutations, but few languages support that.

      A thread – without shared mutable memory – sort of acts like a very light weight OS process. Meaning you can basically roll back the entire state of the thread in one go, and start over from scratch. This is orders of magnitude easier to reason about than having to manually patch up a busted heap when an exception has happened.

  32. Oh I see, when an exception kills the thread, you have the guaranty of not corrupting any state because rust’s thread doesn’t share any state. The relation with STM is interesting.
    That’s what I called writing exception-safe code: no state is modified until the last step, and then the old state is replaced by the new state atomically.
    Threads without shared mutable memory offer a stronger guarantee that the code is correctly written that way.

  33. Your debate with rfielding on whether NPE should be allowed was a very interesting read.
    When you have a nullable value that you know will never be null, sometimes the right choice is actually to throw a NPE. I think it can be fairly common, i.e. in all pure functions.
    So if you don’t have a dereferencing operator for nullable, you will end up needing a “T NotNull(T? t)” function in the standard library that checks and throws NPE so you can write “NotNull(foo).bar” when you know it’s safe to throw.
    My opinion is that it’s all right to have separate operators: ‘.’ for dereferencing non-nullable, and say… ‘->’ for dereferencing nullables or throwing NPE. Visually one can tell easily that code that use ‘->’ can throw. And chaining, i.e. ‘a->b->c’ is certainly more readable than ‘NotNull(NotNull(a).b).c’
    So this wouldn’t be a deal breaker for me.

    • It’s mostly cosmetic at that point, but I do prefer making it less convenient to work with nullables so you’re forced to “deal with it” rather than use the -> operator everywhere as if there was no problem.

      If you have to explicitly say something like e.g.
      null_cast(p)
      {
      // p changes type from nullable to non-nullable
      p.foo();
      }
      else
      {
      assert(false, “this pointer can never be null here”);
      }

      Then there’s much less chance that you will just accidentally ignore cases where there really is a legitimate chance the pointer can be null and you really do need to handle it. It also encourages you structure things so you do a single null check early on and then deal only with non-nulls from there on in (meaning fewer runtime checks needed). It just generally makes it clear that nullable pointers have *no* operations on them – the only thing you can do is do a checked cast to a non-nullable one.

  34. I haven’t really had a chance to think about it too much, but in C++ it should be possible to use the type system to eliminate null pointer exceptions.

    • Yes, something like this: https://gist.github.com/ssylvan/5654932

      If you enforce that nobody ever deals with naked pointers, and just uses these classes instead, which of course isn’t nearly as good as just having it built-in to the language itself.

      The problem is of course that C++ is so full of null pointers elsewhere that they’ll still seep in anyway, and more importantly the language just wasn’t designed for it so you’ll end up with somewhat clunky code to deal with it (e.g. constructor logic can be a bit messy – you’ll probably end up with null pointers just to support complicated construction).

      • No, having something built into the language versus being a library makes little difference. What you are really saying is that you’d like all programmers to be forced to program a certain way due to the language. Even with current C++ you shouldn’t really be using naked pointers if they refer to the heap–you should be using unique_ptr or shared_ptr. In other cases you could use references. The problems with them “creeping in” would only be due to using libraries written by others, but this is a problem with C++ in general. The language has a lot of features and if you restrict yourself to the subset you need in order to simplify things, then here comes something from a library that wasn’t programmed to your restrictions. That doesn’t always keep you from programming your library exactly how you want though.

      • If you’re adding a feature, having it as a library is fine, if you’re trying to prevent misuse of something, having the compiler complain is pretty important.

        This is a turing tarpit argument. “Writing C++ is perfectly safe so long as you never do X, Y, Z and never make any mistakes, and never use any libraries that use X,Y,Z and nobody else on your team never uses X,Y,Z….” and on and on. You can make the same arugment about C or Assembly. The safety and confidence you get from knowing that the compiler *will* prevent certain errors comes from the compiler also enforcing it consistently.

        Plus, as I explained, you need language support in this particular instance to be able to do it well. There are too many instances in C++ when you’re forced to deal with partially constructed values (which often means null pointers). Think about all the cases where your constructors have actual bodies, rather than just using the initializer list.

        You need the language to be designed with non-nulls in mind so that you don’t end up with nulls just to work around the language. It needs to support a richer form of object construction.

  35. I too like static typing, particularly Standard ML, but lately there come Shen, it’s Lisp with pattern matching and optional static typing, you should check it out: shenlanguage.org

  36. Pingback: Option[T] > null | prose :: and :: conz

  37. You mentioned checking out Elm and FRP. Come to the mailing list! We’ll be very open to hearing your thoughts, and I’d like to know how to better present Elm so that it doesn’t seem confusing on the surface. Theoretical and practical elegance is an essential requirement of Elm’s features, and there are plenty of interesting and uncharted issues we’re trying to solve in the best way.

    https://groups.google.com/forum/?fromgroups=#!forum/elm-discuss

  38. I’m actually working on a language to address the major shortcomings of C++. It aims to reside in the same low level domain while offering performance-sensitive high level constructs to aid in development.

    How far are you willing to bend on item #4? It is the one area I feel I am unable to address. I really do not want to introduce a full blown garbage collector (and fundamentally alter the language to have a lot of runtime ‘magic’ going on). I really like the concepts of object ownership and careful reference handling, but I feel that adding automatic memory reclamation would start down a road of no return.

    Thoughts?

    • The issue is really about safety. So you have two options, either have some kind of gc, or don’t be a general language (i.e. don’t support shared ownership of data). You could enforce recounting for that, I suppose, but without a cycle collector you get leaks, which I suppose is better than crashes, but still not ideal.

      Note, I think it’s perfectly valid to not be a general language. I.e. don’t support shared ownership. It’s not as commonly required as you might think, and if the language discourages it even less so. You can generally emulate it when you really need a graph or something (arrays of objects with indices being your “pointers”) which in principle just means you reintroduce manual memory management, but in practice these things end up being very localized (effectively one isolated heap per data structure, or system), so easier to get right, and runtime index checks catch errors quickly.

      • Yeah, this was more the direction I was looking. Controlled memory block lifespans and static analysis of heap handling could go a long way to making manual memory management attractive again. Given a choice, I want to error on the side of trusting the developer to make good decisions. I’m sure, at a later point, I’ll create my own scripting language, where I will implement lots of I-do-not-trust-the-developer features.

      • I’m suspicious of the “trust the developer” argument for two reasons:

        1. Most of the time it’s used as a excuse for poor language design. It’s been used to defend unsafe choices since asm vs c.
        2. The more experience I get, the more I realize that I’m actually quite stupid! The vast majority of the time the compiler complains, it’s because I’ve actually made a mistake. I don’t *want* the compiler to trust me, I want it to prevent me from screwing up (which I will do, because I’m stupid). Erring on the side of trust is the wrong default, because humans aren’t very trustworthy when it comes to keeping lot of details straight.

        I don’t mind an “unsafe” keyword where you can “trust the developer”, but normal code should require as little trust as practical to avoid crashes.

      • I only error on the side of trusting the developer if it is a really close call. I’m sure if we were to flesh out this discussion, we’d agree on most points. I agree that existing languages choose stupid things to trust the developer with. (I’ll never forgive Java and C# for making strings nullable.)

      • That’s a really good link. It still doubt I’d be able to implement that concept into the language, but it gives me a few ideas.

        Generally speaking, I want my language to be a cleaned up and simplified C++. I want to keep as much of the power as possible. That includes manual memory management. However, I also wanna make a scripting language before I die, so real time garbage collection could work there. 😛

  39. would love to hear your take on mythryl, which partially doesn’t satisfy #4 and fails at #5 & is still very much work in progress but aims to provide an ml-language accessible and comfortable for c/c++ programmers. http://mythryl.org/

  40. Sebastian, if you don’t utilize the C-language-interface rule-relaxation feature-set of Ada (e.g., the various easily-grep-able language features that usually begin with “Unchecked_”: Unchecked_Access, Unchecked_Conversion, Unchecked_Deallocation, and Unchecked_Union), then Ada does in fact satisfy your #2 and #3 criteria.

    For a taste, please see
    http://www.enyo.de/fw/notes/ada-type-safety.html

    For a deep dive of the mathematically-provably correct object-lifetime regulations throughout the Ada language that these Unchecked_* features defeat, please see the Ada Annotated Reference Manual and ISO8652:2012 Ada Language Reference Manual, both available at http://www.adaic.org/ada-resources/standards/ada12

    GNAT Pro, GNAT GPL, and GNAT FSF (i.e.,the 3 Ada2005/Ada2012 compilers currently available) have the ability to read in a C or C++ header and dump out the equivalent Ada-language declarations as a thin & very intimate binding of Ada to C or C++ (complete with numerous usages of the aforementioned Unchecked_* features). From these ABI-based bindings, highly-Ada-centric thick bindings can be written. For Windows, GNAVI (and its GNATCOM and GWindows) is one such thick binding to Windows, COM, and ATL. (Disclaimer: The 2004-era C-interface layer in neither GNAVI nor GNATCOM nor GWindows were generated from GNAT’s capability of dumping out the C/C++ GCC symbol table back out as Ada, which appeared only in the very last few years, but nowadays writing such Ada translations of massive APIs is easier than ever, due to this automation replacing the bulk of the manual effort to design a thin binding.)

    • This seems to indicate otherwise: http://en.wikibooks.org/wiki/Ada_Programming/Types/access

      “Objects of access types are implicitly initialized with null, i.e. they point to nothing when not explicitly initialized.”

      Is that site incorrect? Yes, it’s nice that you can opt-in to null safety, but the actual solution is to simply disallow following a pointer that may be null altogether – so you would have to first do a (safe) conversion to a non-null pointer before you could do anything with it.

      • In the same page, “Access types should be used rarely in Ada. In a lot of circumstances where pointers are used in other languages, there are other ways without pointers”
        Coding without pointers is really easy but totally confusing when coming from C/C++.

      • Sure, but rarely isn’t never. I’m willing to believe that some languages promote a style that avoids pointers better than others, but the fact remains that this particular runtime crash can be completely eliminated at compile time, and yet Ada doesn’t.

  41. If one does not want accesses of any null-object (in the Ada jargon, which would be “if one does not want to deference any null pointers” in the C/C++ jargon), then one must declare all accesses to be “not null” which causes the compiler to assure that each access always accesses an extant object, even at initialization-time. In Ada parlance, always declaring accesses to be “not null” in Ada2005 or Ada2012 (because “not null” and the associated compile-time proof by mathematical induction that null is never introduced in the chain of custody were absent in Ada1983 and Ada1995) seems to fully satisfy your criteria #2 & #3, refuting even your initialized-to-null concern about accesses.

    Or in other words, omitting “not null” on an access type declaration is a mere respelling of “a C/C++-style pox on all your pointer houses”, so in Ada20XX don’t omit “not null”, because there be dragons that can destroy the (would-have-been) perfect assurances of the (would-have-been) perfect chain-of-custody.

    http://www.adaic.org/resources/add_content/standards/05rat/html/Rat-3-2.html

    • No, that’s not enough. You can write a wrapper pointer type in C++ that doesn’t allow nulls too (I linked one such implementation in a comment above), but that doesn’t mean the problem is solved.

      The bottom line is that Ada allows runtime crashes due dereferencing null. Saying “not if you always do X” isn’t a good argument, the point is that the language should rule it out by construction, not rely on the carefulness of the programmer. I’m not claiming it’s impossible to avoid crashes due to null pointers in Ada (or in C++) if you’re careful enough, I’m claiming it’s possible to make mistakes and end up crashing due to null pointers.

      Any argument that depends on the programmer being consistent and not making mistakes is pretty much invalid. If it wasn’t you could claim that C was type safe (“Just never do any type-unsafe casts!”).

  42. It’s surprising that most languages don’t handle pointers like this ADA example. It seems to be the answer to ‘the billion dollar mistake’.

  43. As noted in the Ada example noted in the link: this changes where the exception happens. It’s a good thing. It forces it to happen where the assumption that the pointer is valid begins, rather that at some random de-reference deep inside the code. in cases where null was a legitimate value, you simply branch on and if statement so you can either ignore or pass that nullable pointer on to whatever needs it. you can only eliminate the assign-to-not-nullable errors by explicitly handling the null case; which often is a bogus branch that should throw an exception and retreat to the fault barrier to ensure uniform handling of this stuff that is code bugs without a specific handling plan (array out of bounds, nullassigntonotnull, … ) to either roll back transactionally or roll-forward for that iteration of the fault barrier.

    Not only should not-nullable be the default, but there should be *no* implicit casts from nullable to not-nullable. ie: w := (nullable)wptr; foo(w); should compile, but not foo(wptr); Using Go syntax type inference to eliminate type stuttering.

    • Again, the bottom line is that in Ada you can still access a field on an object of access type and have the program crash because that reference is null.

      Yes, if the programmer is careful he can use a special opt-in non-null type everywhere (just like you can in C++), but relying on the programmer doing everything right is not the same as ruling it out by construction.

      Adding a non-nullable pointer is an improvement, but the key is that *nullable* pointers must not allow member access. As long as you allow people to dereference null, they will. Nullable pointers should only have a single supported operation – an explicit cast to non-nullable (which can fail).

      • http://rise4fun.com/specsharp/ – Spec# is so close… per sebastiansylvan’s last point, only thing that’s still wrong with it, and it means everything. Make a function that takes a “string” argument, and you can de-ref members on it, which should only be possible if it’s of type “string!” (the ! is non-nullable in Spec#). If you add in an explicit null assignment inside the function body before the de-ref, the compiler does catch it. But if you stretch it through multiple function invocations, it misses it. This is a good example of how the nullable-by-default pointer is the wrong choice. It’s a severe language design bug, which is easily demonstrated by looking through bug reports and code repos on any large project.

  44. Sebastian, you are incorrect in implying that Ada2005’s “not null” offers absolutely no more functionality than can be offered by a C++11 library. If a programmer always utilizes “is not null access” instead of mere “is access” in access type declarations (and always refrains from usage of Unchecked_* C/C++-compatibility features, enforced by mere regular-expression search of the source code) instead of mere “access”, then an Ada2005-compliant compiler will prove (by lack of error messages, not mere warnings, not mere runtime exceptions) that no access to null was even possible anywhere throughout the program. An Ada2005-compliant compiler performs this proof by mathematical induction at compile-time by assuring that a null-object assignment to access was never introduced implicitly or explicitly anywhere in any nook & cranny of the (implicit or explicit) source code.

    No mere library in C++ (or Ada95 or earlier) can make such a compile-time assurance. Indeed, even if an equivalent of “not null” were added to C++ language itself, C++ could not make that assurance at compile-time for much the same reason that C++ could not assure the validity of throw-specifications across compilations units. Why? because C++ has (weak) independent compilation of compilation-units, not Ada’s (strong) separate compilation of compilation-units. [ See http://en.wikibooks.org/wiki/Ada_Programming/Packages#Separate_compilation ] An Ada2005-compliant compiler can assure that the validity of the “not null” mathematical proof at compilation-time is not blinded (i.e., corrupted) by compilation-unit boundaries. (Unless the entire compiler is implemented in the Turing-complete metatemplate-programming of C++ templates, which would be impractically complex to do), no mere library can accomplish such a compile-time guarantee.

    Sebastian, A) enforcement of “is not null access” via regular-expression search of the Ada2005-compliant source code is in an entirely-different league than B) a C++11-compliant library’s detection at run-time of assignment of null to a wrapped pointer and than C) debugger-time looking for the null-pointer-deference needle in the combinatorial-explosion combination of just the right combination of branches of code to place the accumulated state in the null-pointer-deference situation. (Even 100% code-coverage testing does not cover the full state-space of #C above—hence the needle-in-the-haystack search accomplished by field deployment, then waiting for crash bug-reports to come back from the field.) Futhermore, (because C++ with C-preprocessor is able to parsed via a GLR-or-more-expressive grammar), C++ lacks a practical LL(k)-based or LALR(k)-based or regular-expression-based way of assuring that all programmer’s behaved themselves by not bypassing the library’s wrapped-pointer or utilizing leaked raw addresses (i.e., pointers or references) as they are visible in C++ at point of assignment or of dereference.

    • I think you’re absolutely wrong about that, but if not please explain yourself. As far as I can tell it would be able to do everything that Ada could do – your argument about compilation boundaries does not factor into at all.

      If you never use naked pointers (e.g. wrap allocation functions to return non-nullable pointer types instead of pointers, etc.) the compiler will prove at compile time that you can’t dereference null – the type checker would complain.

      Compilation unit boundaries do not factor into it – a float* is not the same type as a ptr<float>, and, assuming you don’t provide any implicit conversion, the compiler will ensure that the former will never be treated as the latter.

      You’re missing the point though: providing opt-in safety (i.e. “Just remember to always do X”) is no safety at all. As long as Ada allows you to dereference a nullable access type you can’t be safe. Yes, if you have all the source code you could use special tools to find unsafe patterns and warn about them, but that’s a far cry from eliminating the issue altogether by design.

      EDIT: in fact, I believe the C++ library ( https://gist.github.com/ssylvan/5654932 ) is actually better than what Ada can do with its built-ins, because it also provides a safe nullable pointer. Unlike a native pointer it’s safe because it doesn’t give access to the underlying memory without an explicit check. I.e. you can’t access a member field or method on an object through the nullable pointer, so no unexpected crashes. So with the C++ library you don’t have to forego nullable pointers completely to be safe – you just have to avoid the “native” pointers (T*) and stick to the two library pointer types (ptr and ptr_opt).

  45. Sebastian, no, it is you who is missing the point: You ponder devising some fanciful new language from scratch when all that you need to do is:
    1) download the GNAT source code which is currently Ada2012-compliant in Free Software Foundation’s GCC 4.8 or AdaCore’s GNAT GPL 2013;
    2) change the default from nullable access types (which is backwards compatible with Ada95) to “not null access” so that the “not null” is presumed by every use of “access”;
    3) examine the grammar of Ada2012 to realize that there has never been any “opt out” mechanism; (Therefore, there are longer any “opt in” mechanisms and there never was any “opt out” mechanisms, which satisfies your strong desire for nonoptionality.)
    4) build;
    5) use this language that fully satisfies all 5 of your deal breakers.

    Regarding, your misreading of http://en.wikibooks.org/wiki/Ada_Programming/Types/access
    “Objects of access types are implicitly initialized with null, i.e. they point to nothing when not explicitly initialized.”

    The “not null access” null-exclusion means that null has been removed from the set of valid values for instances of that subtype. Hence, when an instance of a “not null access” type is left uninitialized in textual source code, one portion of the compiler will implicitly attempt to initialize it to null as if “:= null” overtly appeared in the source code (as you read), but then another portion of the compiler will check whether this attempted assignment will satisfy the declared constraints, one of which is “not null”. Therefore, this ill-fated attempt to implicitly initialize this instance of a null-excluded access-type will emit a compile-time error, precluding all possibility of dereferencing null at run-time (because a nonextant executable that is not yet built cannot run, hence no run-time, hence no run-time null-object access).

    • I haven’t misread anything, please pay closer attention to what I’m actually saying.

      I understand that Ada won’t allow you to assign null to a non-null pointer (and will fail at compile time if you try), and have never said anything else.

      All I’ve said was that you can achieve compile-time checking for null using a library in C++ (which is true, whether you understand how it works or not), if you’re prepared to make the same requirements that you’re willing to impose on Ada programmers (that they consistently use a particular type).

      I’ve also said that being able to opt-in to safety (by adding “not null” to a type) is not the same as getting rid of the unsafe feature. To fix Ada you would have to *remove* the ability to access data directly through an object of access type (unless that access type has the “not null” attribute).

      Anyone claiming that their language is safe, when it’s only safe if the programmer is careful and consistent in doing something that isn’t the default, is disingenuous at best.

      Yes, taking an existing language and modifying it would be one approach to achieving what I want. However, it’s obviously not enough for just *me* to use it, it must be an actual language supported by tooling and standards, and have other people using it too. If someone were to invest in solving the null issue in Ada (which probably wouldn’t be hard – though merely switching the default isn’t enough, as I’ve explained about five times now), I’d seriously consider using it (I like Ada otherwise). Eiffel took the hit of non-backwards compatibility to solve the null issue, IIRC.

  46. Sebastian wrote: “solving the null issue in Ada” … “though merely switching the default isn’t enough”

    You & I have discussed the “not null” null-exclusion of accesses (probably enough for you, I, and the audience of posterity). Please itemize these other areas of Ada2012 where you claim that Ada2012’s proof by mathematical induction is broken, allowing memory corruption, null-object access, or some other cause of crash that the non-deal-breaker language would solve (or solve better).

    Please feel free to reference the following normative references:
    http://www.ada-auth.org/standards/ada12.html

    • I already have. Again: simply allowing someone to opt-in to safety (by being very careful and never making mistakes) isn’t the same as eliminating the problem.

      Switching the default would still allow someone to declare a nullable access type (these will still be needed) and then crash the program by dereferencing it while it’s null. The only solution is to not allow dereferencing on nullable access types at all (the only thing you could do with such a type should be an explicit cast to a non-nullable type – ideally in some way which doesn’t let you ignore the failure case).

      There are likely other issues but I don’t know Ada well enough to comment. In a language like C# you would have to revamp the entire construction logic (the body of a constructor sees the object in a partially constructed state, which we can’t allow if we want most pointers to be non-nullable). These issues wouldn’t be critically fatal, they’re more in the realm of changing the ‘flavor’ of the language to encourage non-nullable pointers in more cases (and avoid places where people have to use nullable pointers to work around the language).

      • Actually, a NotNull member that is initialized via the constructor would end up behaving like final variables do in Java/C# already; except it doesn’t complain about re-assignments. No?

      • The problem is that you can call a virtual method call from within the constructor.. Or hell even a normal method call. So basically any method has to live with the possibility that the object may be partially initialized.

        You could add some restrictions to prevent that, but I suspect what you really want to do is change constructors so you never end up with that problem in the first place – my suggestion would be to have only a single constructor form, a C style struct initializer. Then all “constructors” would just be static methods that “pre-prepare” all the members of the object on the stack and then copy them into the actual object in one atomic step at the end (in the return statement). That way the object is never partially constructed. Obviously you’d try hard to optimize those copies away (construct the values in-place), but semantically it would be a copy.

      • Actually, in Java: you have to call super(…) as the first line. Scala (and Go, other languages) have a default constructor that’s a struct initializer, and this should be the one that ultimately gets called, no matter which constructor you invoke. But alternate constructors could invoke pure functions to transform or generate the initial values. Foo(T val1) { super(val1,fInit1(val1),finit2(val1),…);}. But, yeah… I have on one occasion run into a situation where I had *all* final variables in a class, yet some construct in the language allowed me to see that value as null while debugging.

        Technically, what they are doing in ADA is going to be correct. But I agree with you… Just syntactically purge the dereferencing of nullables so that there is no analysis to perform. Due to concurrency, these expanded type annotations are unavoidable anyway. Message passing between processes to avoid locks changes everything. You need at least these kinds of reference annotations on types to even begin: nullable,(deep) immutable,exclusive mutable (can reference exclusive or immutable only – a linear type). This is just reflecting the choice to allow sharing of immutable data among threads, or force exclusive ownership of mutable data to pass between threads.

        I would go so far to say that in any reasonable language, the default should be notNull as well as immutable (val vs var). With that default, you don’t have to redundantly specify the type in declared identifiers in most cases. You only need to specify the type in cases where the value may later be re-assigned to a more general type than it was initialized with.

  47. I apologize for the bad HTML markup above. Here is what was intended:

    Sebastian wrote: “Switching the default would still allow someone to declare a nullable access type”

    Via precisely what syntax in the hack to FSF GCC 4.8′s GNAT or to AdaCore’s GNAT GPL Edition 2013? The hack would change is access to have only the semantics of is not null access. The syntax is null access is entirely absent in Ada2005′s and Ada2012′s grammar. So, A) if is access would mean is not null access and B) if is not null access obviously means is not null access and C) if is null access causes a compile-time error, then precisely what Ada2012 syntax “would still allow someone to declare a nullable access type” as you claim? It seems as though your claim results in a falsehood, except when utilizing the Unchecked_* features, pragma export features, pragma import features, and pragma convention features whose use is generally verboten(-by-convention)‡ in Ada2005 & Ada2012 designs other than to interface with nonAda APIs, such as to fully satisfy your deal-breaker #5.

    ‡ = and enforced by grep or other mere regular-expression search of the textual Ada source code

    Sebastian wrote: “(these will still be needed)”

    Those of us who design in Ada2005 and Ada2012 say (and accomplish) otherwise, except when utilizing a thin binding to interface with C or C++ APIs that contain at least one pointer or reference, such as to fully satisfy your deal-breaker #5.

    • There are always cases where you need to optionally refer to something.

      You could remove nullable access types altogether and use some other kind of mechanism to support this optionality (e.g. discriminated unions – a la Haskell’s Maybe type) and that would be fine. The point is that you need to stop allowing people to just say “foo.x” where foo could potentially be null. This is a bigger change than merely changing the default (you’re actually removing capabilities).

  48. Sebastian wrote: “discriminated unions – à la Haskell’s Maybe type” … “This is a bigger change than merely changing the default.”

    It is already there in Ada, so no change to Ada:
    Ever since Ada83 progressing through Ada95, Ada2005, and Ada2012, Ada has long had a rich & evermore-enhanced variant record (in Ada jargon, a.k.a. discriminated union in other jargons) [not to be confused with Ada’s discriminated record, btw], so not only do I not foresee “a bigger change than merely changing the default”, I don’t foresee any change to the Ada2012 language related to modeling designs that utilize exclusively is not null access types, such as via variant record.

    Instead of a change to the already-extant Ada programming language, perhaps you intend the “bigger change” to be to the application designer’s mindset when writing an app’s Ada source code. Under that interpretation, you are correct: designing with such techniques as
    a) the wholesale lack of nullable accesses and
    b) variant & discriminated records
    is a whole different world than commonly practiced in C & C++, because the design is aware of compiler-time-enforced measurement(-by-the-compiler) of extent of lifetime of entities (e.g., records, subprograms, frames on the call-stack).

    • Well if you’re going to remove a feature (in this case nullable access types), that’s a bigger change (in terms of impact) than merely changing the default but leaving some easy way of getting the previous default behavior back (in this case by adding “nullable” or something to the type). Don’t get me wrong, I think you should change Ada like that, I’m just saying it’s not a trivial change to adopt.

      And like I’ve mentioned before, you may want to change the language to make it easier to work with non-nullable pointers.
      E.g. if you want two objects pointing to each other, many languages would be forced to make at least one of those pointers nullable purely to handle the initialization. In other words, the programmer has to use nulls where he doesn’t want them just to work around the language. You’d want to get rid of those kinds of issues, ideally. I don’t know Ada well enough to know in detail if there are many such problems, and what they are, but most other languages with null have plenty of them so I’d imagine it’s likely that you’d want to make some changes.

  49. Of course, nobody does large scale complex programs in Python!!! Who would think of developing youtube in python, used by millions of people to put their videos of cats?… well, not Microsoft anyway, and they are doing so weeeell on the web 😛

    • Plenty of people write COBOL for a living too, it says nothing about the merits of the language. You can do great things with awful tools, it doesn’t mean you shouldn’t try to improve your tools.

    • The fact that dynamic language tools can’t do effective code reviews is never an actual virtue, even if for what you do it isn’t a ‘deal-breaker’. Using a language that is susceptible to good checks is automation of as much due-diligence as possible. In safety/security domains you can go to jail when bugs are found ‘in production’.

      As much as I agree with the Python guys that small and simple code counts as much towards verifying it as a good compiler…. Being unable to prove anything correct is a major problem for the runtime performance too.

      The idea that the 10x performance waste of a dynamic language doesn’t matter assumes that the server is not at 100% utilization. This seems to be why Google is pursuing Go.

  50. Windows compatibility? Meh. Kidding (only partly). Great list. I had the same complaints till I found Rust. Though the type system is fairly complicated, once mastered it meets all of your requirements IMO. The language hasn’t been able to market itself as well as Go and that’s a shame. It’s bound to scare newcomers – complex type system, multiple pointer types, optional runtime, scarce and outdated documentation etc.

    Now to wait for a fast compiler that provides good error messages and a stable release.

Leave a comment