7 votes

Announcing sound null-safety for Dart

8 comments

  1. [8]
    Macil
    Link
    Null-safety is killer. I consider null-safety a big part of type-safety, and I decided a few years ago to avoid picking up any new languages that don't consider it that way. I passed on Dart when...

    Null-safety is killer. I consider null-safety a big part of type-safety, and I decided a few years ago to avoid picking up any new languages that don't consider it that way. I passed on Dart when I saw it didn't have null-safety, but I could see myself happily choosing to pick it up now if I wanted to make a mobile app. Maybe I'll go read about Flutter.

    I've worked on a large (untyped) javascript codebase that was really sloppy with object types and nulls, and we eventually migrated it to Typescript (which has null-safety), which helped us fix and avoid tons of issues, especially around null-handling. At least for large codebases, I really swear by type-safe languages after that experience, but I found it disappointing that most other existing "type-safe" languages don't actually get you null-safety. A big part of the benefit in switching to Typescript was the null-safety. A typed language without null-safety just feels like a worst-of-both-worlds situation: you have to do the work of being explicit about types, but the system won't guarantee that any value is actually that type instead of null. A language with null-safety adds a tiny concept, and then the system fixes that guarantee and makes sure everyone is on the same page about where nulls can go.

    5 votes
    1. [7]
      skybrian
      Link Parent
      It's certainly a welcome change, but in Java I didn't see null pointer exceptions as that big a deal. It's an easy and common mistake to make, but also easy to fix. A stack trace tells you what...

      It's certainly a welcome change, but in Java I didn't see null pointer exceptions as that big a deal. It's an easy and common mistake to make, but also easy to fix. A stack trace tells you what you need to know.

      3 votes
      1. [2]
        stu2b50
        (edited )
        Link Parent
        The thing is, they're runtime exceptions. The only way you can be sure that on all reasonable code paths, you don't get a null pointer exception, is to write a billion unit tests. It's whatever...

        The thing is, they're runtime exceptions. The only way you can be sure that on all reasonable code paths, you don't get a null pointer exception, is to write a billion unit tests.

        It's whatever for your personal project. It's a huge problem for a production quality software. If Facebook null pointers on a common code path (not that they use jvm languages, but pretend), they lose millions of dollars, every minute it's not fixed.

        It's not okay. And the other side of "compile time null checks", is a laborious unit testing process, that also costs hundreds of thousands in employee time.

        Additionally, it's not always a "simple fix". Sometimes an input must sometimes be null, maybe because it's i/o, etc. And then what happens you get something null that shouldn't be? Sometimes you can easily gracefully exit just by early returning. However, it's just as possible that you're in the middle of a function that is very stateful, and as such a graceful exit is NOT TRIVIAL.

        All while the pressure is mounting to fix this shit now cause people are complaining!

        There's a reason basically every language without static types, has static types bolted on it now via corporate libraries.

        4 votes
        1. skybrian
          Link Parent
          Yes, compile-time checks are better, and a runtime exception that actually makes it to production can be pretty stressful, as you say. But usually they don't get that far. Even when testing is...

          Yes, compile-time checks are better, and a runtime exception that actually makes it to production can be pretty stressful, as you say.

          But usually they don't get that far. Even when testing is spotty, the happy path is usually tested, either by a unit test or by ad-hoc testing. If it gets past that point and things are crashing on a common code path then monitoring would catch it and the release would likely be rolled back soon after it started being rolled out. And if you have any decent monitoring, it will report any rare stack traces to you.

          As a result, back when I was doing this at Google I found that when we turned on a new compiler warning in production code, we'd find a lot of bugs, but they were mostly on code paths that for whatever reason were never or seldom used. This is basically survivorship bias - if the bug was really important then it would have been fixed. We'd fix them anyway but it was somewhat rare to find something that a real user could trigger this way.

      2. [4]
        Greg
        Link Parent
        I think the big win with null safety is catching that class of errors before they happen - you get the report at compile time, rather than seeing it in a server log at 4am for a function deep...

        I think the big win with null safety is catching that class of errors before they happen - you get the report at compile time, rather than seeing it in a server log at 4am for a function deep within the codebase that you absolutely swore had the correct testing and input validation.

        The other option, and one that I'm kind of surprised I haven't seen in more languages (or standard libraries, I guess), is accepting null as a perfectly valid input by default. Null goes in, null comes out - it doesn't cause exceptions because functions are expected to handle it gracefully.

        1 vote
        1. Macil
          Link Parent
          Lisp (or some dialects of it) seems to lean in to this, but it drives me crazy. It's weird that it pushes the convention of every function also working as a no-op. It works okay for simple...

          The other option, and one that I'm kind of surprised I haven't seen in more languages (or standard libraries, I guess), is accepting null as a perfectly valid input by default. Null goes in, null comes out - it doesn't cause exceptions because functions are expected to handle it gracefully.

          Lisp (or some dialects of it) seems to lean in to this, but it drives me crazy. It's weird that it pushes the convention of every function also working as a no-op. It works okay for simple functions that do nothing but use standard functions directly on their parameters, because then the function does nothing on receiving a null without any extra effort, but it's super awkward for functions that do some outside calls / side effects in addition to dealing with the parameters, because then you have to add explicit null-checks to make that function no-op on nulls too. I'd rather the system pushed a convention of failing fast (ideally at compile time, like what null-safety gets you) on unexpected nulls.

          2 votes
        2. [2]
          skybrian
          Link Parent
          Yes, all compile-time checking has that advantage. But some runtime errors are easier to fix than others, and null pointer exceptions are particularly easy. The worst bugs cause no runtime errors...

          Yes, all compile-time checking has that advantage. But some runtime errors are easier to fix than others, and null pointer exceptions are particularly easy. The worst bugs cause no runtime errors to be reported at all.

          Generally speaking it's better to fail early than late. It's usually better for a public API to check for nulls immediately (along with other precondition checking) and fail with a clear error at the library boundary than to try to keep going and hide the issue.

          1 vote
          1. [2]
            Comment deleted by author
            Link Parent
            1. skybrian
              Link Parent
              For nulls I think compile-time checks are better, but for argument-checking in general I think attempting to add every check you might want to do to the type system would make public API's very...

              For nulls I think compile-time checks are better, but for argument-checking in general I think attempting to add every check you might want to do to the type system would make public API's very complicated.

              For example, you can end up in a situation where there are multiple kinds of strings for different situations, and the function you're calling inevitably takes a type that you don't have.

              1 vote