Favorite Programming Language Features: Swift’s Exception handling with Optionals
I spent the early 20-teens writing mostly Java (or at least JVM-ecosystem code) backend code, and then spent the back half of the teens writing mostly Swift on iOS. (Before that? .NET, mostly.) I seem to be on a cycle of changing ecosystems every half decade, because I don’t write a tremendous amount of code these days, but when I do, I’m mostly back writing Java. It’s been a strange experience being back. While Java has moved forward somewhat in recent times, it’s still fundamentally the same language it’s been since the late 90s, and so going from Swift back has meant suddenly losing two decades of programming language development.
Let me give you an example of what I’m talking about, and at the same time, let me tell you about maybe my favorite language design feature of all time.
Let’s talk about Exceptions.
For everyone playing the home game, Exceptions are a programming language feature whereby if a section of code—a function or method or whatnot—gets into trouble, it can throw an exception, which then travels up the call stack, until an Exception Handler catches it and deals with the problem.
The idea is that instead of having to return error codes, or magic numbers, or take a pointer to an error construct, or something along those lines, instead throw an exception, and then you can put all your error handling code in one place where it makes sense, without having to tangle up the main flow of the program with error condition checking.
They’re pretty great! There was a lot of grumbling I remember from around the turn of the century about if exceptions were just GoTos wearing groucho marx glasses, but in practice they turned out to be a deeply useful construct.
Like many great ideas, exceptions came out of the LISP world , and then knocked around the fringes of the computer science world for a few decades. I remember them being talked about in C++ in the mid-90s, but I don’t ever recall actually seeing one in live code.
The first mainstream place I saw them was in Java, when that burst into the scene in the late 90s.
(I could do that exact same setup for garbage collection, too, come to think of it. I’m deeply jealous of the folks who got to actually use LISPs or Smalltalk or the like instead of grinding through fixing another null pointer error in bare C code.)
And Java introduced a whole new concept: Checked Exceptions. Checked exceptions were part of Java’s overall very strong typing attitude: if a method was going to throw an exception, it had to declare that as part of the method signature, and then any place that method was called had to either explicitly catch that exception or explicitly pass it on up the stack.
Like a lot of things in early Java, this sounded great on paper, but in practice was hugely annoying. Because the problem is, a lof of the time, you just didn’t know! So you had to either catch and hope you dealt with it right, or clog up your whole stack with references to possible errors from something way down lower, which kinda defeated the whole purpose? This got extra hairy if things threw more than one kind of exception, because you had to deal with each one separately, and so you end up with a lot of copy-and-pasted handlers, and the scopes around the try-catch system are always in the way. And, even if you did carefully check and catch each possible exception, Java also includes RuntimeExceptions, which are not checked, so any method in a library you depend on can throw one without you knowing about it.
So in practice, a lot of programmers ended up just using Runtime Exceptions, and then that lead to a lot more other programmers handling exceptions “Pokeman Style” (“Gotta catch ‘em all!) and just catching everything without much in the way of handling.
It’s a perfect example of a safety feature being annoying enough that it actually makes the whole thing less safe because of the work people do to avoid it instead of use it.
So when Microsoft hired “the Delphi Guy” to do a legally distinct do-over of Java in the form of C#, the result was a langage with only un-checked exceptions. You could catch them if you wanted to, and if you knew what to do? Otherwise it would run on up the call stack and end up in some global error logger. This is model most other languages from the era used.
And this also kinda sucked, becase even if you didn’t really care what the error was, you still wanted to know something happened! So you ended up writing a lot of code where in tesiting you discovered some exception being thrown from some library and messing up the whole stack you didn’t even know could happen.
Because here’s the thing—most of the time you don’t care what the details of the error were, you just want to know if the whatever it was worked. Call to a web service, number format conversion, whatever, we don’t care why it failed, necessarily, but we sure like to know about it if it did.
And so we come to Swift. Swift is one of those languages that was seemingly built by looking at how every other language handled something and then combining all the best answers. (Personally, I enjoyed tremendously that the people making decisions clearly had all the same taste I did.)
This caused quite a stir when it happened, but Swift reintroduced checked exceptions, but with a twist. No longer did you have to say which exceptions you were throwing, a method either declared it threw exceptions or it didn’t. A method that did not declare that it threw couldn’t throw any sort of exception, runtime or otherwise.
Swift has a lot of features that are designed to make the code easier to read and think about, and but not necessarily easier to write; not syntactic suger, syntactic taco sauce, maybe? One of these is that you have to type the keyword try
in front of any method call that says it can throw an exception. This really doesn’t have any purpose other than reminding the programmer “hey, you have to do something here.”
And this is great, because you get some very cool options. Since individual exceptions are not checked, you can opt-in to handling individual exception types or just the fact there was an exception at all. This dovetails great with the fact that in Swift, exceptions are enumerated types instead of classes, which is a whole article on its own about why that’s also brilliant, but for our current purposes it makes it very simple to go from handling “an error” to handling the specific error type.
But! There’s an even better option for most cases, because Swift also has excellent handling for Optionals. Optionals are “just” a reference that can be null, with some excellent extra syntactic support. Now, in Java, any referece can be null, but there really isn’t much in the way of specific support for dealing with null values, so Java code gets filled with line after line of checking to see if something has a value or not.
Swift does a couple great things here. For starters, any reference that isn’t explicitly defined as an optional can never be null, so you don’t have to worry about it. But there’s also a bunch of really easy syntax to look at an Optional, and either get the “real” value and move on, or deal with the null case. My favorite detail here is that the syntax for an optional in Swift is to put a “?” after the name of the variable, as deltaPercent?
, so even glancing over a screen of code it reads like “maybe this is here?” Building on that, Swift has a guard
construct that you can use to check an incoming Optional, handle the null case and exit the method, or get the real value and move on. So it’s a pretty common idiom in Swift code to see a pile of null handling at the start of a method, and then the “normal” flow after that.
Combining Optionals and the new approach to exceptions, Swift provides a syntax where you can call a method that throws and instead of explicitly handling any exceptions, just get the potential result back as an optional. If an exception case happens, you don’t need to do anything, it sets the optional to null and returns. Which is fantastic, because like we said earlier, most of the time all we really care about is “did it work?” So, the optional becomes a way to signal that, and you can use the robust Optional handling system to handle the “didn’t work” case without needing to catch an exception at all.
This also encourages what I think is a very solid design approach, which is to treat a method as having two possible kinds of return values—either the successful value or an error, and you have your choice of receiving the error as a specific exception type, or just a as null value. (And subtly underscoring a method with no return value but that can throw an exception is probably a mistake.)
Brilliant!
It’s so great, because you can swap out all the error-case handling for the no-value case, and just get on with it. Systems that enforce strict error handling do it with an almost moral tone, like you’re a bad person if you don’t explicitly handle all possible cases. It’s so nice to use a system that understands there are only so many hours in the day, and you have things to get done before the kids get home.
Exceptions-to-Optional really might be my favorite language feature. I’m missing it a lot these days.