I love the idea of sound null checks, but doesn't Dart also have unchecked exceptions? To me it seems a bit weird to focus so heavily on type soundness when at any moment a piece of code could throw an unexpected exception that kills your program. I realize these are very different features, but a language is more than the sum of its parts. While I realize every language has some level of unchecked exceptions out of necessity (ie. panics on divide by zero, etc.), they are not meant as general level error checking, but as more of a "can't continue - things are that bad" kind of mechanism.
In summary, if they are that convinced sound null types are needed, to me it needs better error handling that matches the same level of discipline. That said, any and all improvements are always welcome, so I do think overall it is cool they are taking this seriously.
TypeScript has unchecked exceptions too, but null-safety is my #1 motivator for using TypeScript over JavaScript
You're speaking from a place of general philosophy, but in practice (at least in my experience), the great majority of unchecked exceptions come from null references. In the TypeScript codebases I've worked on, the only runtime exceptions we really have to think about come from network requests and (unfortunately) JSON.parse(). You quickly learn to always handle those two cases, and with that we see basically zero unhandled errors in production
Of course we're talking about Dart and I don't know how commonly Dart throws unhandled exceptions or where they might come from, but I think it's a dramatic over-simplification to suggest it's not even worth trying to reduce errors just because unhandled exceptions are technically allowed at the language level
Are there any popular languages which don't have unchecked exceptions? Java, for example, supports both checked and unchecked exceptions. In practice, checked exceptions seem to cause more problems than they solve for large scale software development. If you want to inherit from a class or interface it becomes impossible to add new checked exceptions to method signatures. So developers have to resort to hacks like wrapping the new checked exception in an unchecked exception.
This is a pet peeve of mine, checked exceptions map one-to-one to Result types, and in my opinion the former is strictly better (it automatically unwraps the ok result on successful execution, while makes the error handling as tightly scoped as one wishes (try block, leaving it out and auto-bubbling up) with native language support. Plus it includes proper stack traces which are a must in production systems (as much as some people hate them).
Now don’t get me wrong, Java’s implementation leaves much to be desired, inheritance is not the correct choice for denoting it, but I feel we really didn’t give it a proper choice.
Neither Rust nor Go have exceptions at all, and I would consider them "popular" at thos point. To be fair, they each also make you think more about your error handling (there are multiple valid approaches in each language).
Rust does have a feature called "panics" that is similar to exceptions in other languages. A panic in Rust is an unrecoverable error that can occur at runtime. Unlike exceptions, however, panics are not caught automatically by the language runtime. Instead, panics are propagated up the call stack until they reach a "catch point", where they can be handled by the programmer.
1. An exception is a recoverable error, that's the entire point, that's what catching an exception is.
2. Unlike Go's, rust's panics do not actually, universally, get "propagated up the call stack until they reach a "catch point", where they can be handled by the programmer". There's a compiler flag which can be "unwind" or "abort". In the former case (the default), panics can be caught and recovered from. In the latter case, the program gets hard-stopped on the spot.
> Unlike Go's, rust's panics do not actually, universally, get "propagated up the call stack until they reach a "catch point", where they can be handled by the programmer". There's a compiler flag which can be "unwind" or "abort". In the former case (the default), panics can be caught and recovered from. In the latter case, the program gets hard-stopped on the spot.
There are Rust libraries (e.g. salsa, used in rust-analyzer itself) that use unwinding internally for non-local control flow and won't work with unwinding disabled.
2. In default Rust config, unhandled panics end up unwinding the stack, calling destructors for everything, freeing resources, closing files and sockets, and printing an error message and possibly a stack trace. I think this qualifies as the language runtime automatically catching the exceptions.
3. For 99% of users, panic = unwind in rust. If you play with compiler flags, C doesn't have undefined behaviour because ubsan will abort programs if you compile with the right flags.
Honestly every language needs Option and Result types (and exhaustive pattern matching to deal with them). They were invented 50 years ago and languages still come out without them.
The thing that is unfortunate is that Result kind of encourages early failure and bubbling up vs actually handling the error and recovering or crashing.
The other thing you don't see much in Result heavy code is coalescing multiple errors (like for example, most parsers in Rust just bail out at the first error when you want all the errors at once). It's possible to write code that does that, and exceptions have the same problem, but it's still a bit cumbersome.
The real frontier of error handling is algebraic effects, or resumable exceptions.
I've been a mobile dev for many years now. Every team I've been on either largely avoided Swift for the first 3-5 years, or was burned hard due to the constant churn.
It's much more stable these days, but personally I'm still wary of the team's (past?) proclivity to make sweeping incompatible changes.
How many Swift users consider that a good thing though? Swift is on railroad tracks: you have to upgrade on Apple's schedule or eventually you won't be able push your product to the store. Also, 2 to 3 was a nightmare and the Swift team itself is actually pretty chary about doing anything like that again.
In summary, if they are that convinced sound null types are needed, to me it needs better error handling that matches the same level of discipline. That said, any and all improvements are always welcome, so I do think overall it is cool they are taking this seriously.