Exceptions Considered Harmful?

The other day when I was scrolling LinkedIn, I found a post arguing that exceptions are not the best way to handle errors. Most languages don’t force you to handle or re-throw exceptions at each level of the call stack, which means that it can be difficult to track down the source of a given exception when it bubbles up to the top level. In addition, exceptions should only ever be used to address situations that are, well, exceptional, and not situations that can easily be anticipated – say, a record not being found in a database.

Error Statuses in JSON Data

I’ll share the solution that was suggested in the post momentarily, but first, I’d like to share an old solution from the days before I knew much about the REST maturity model. In those days, an “Ajax” request was just a POST with a uniform JSON response. Since it hadn’t occurred to me to use HTTP response codes, I needed to indicate success and error statuses in my JSON, so my responses usually ended up looking something like this for a “failure”:

{ error: "User not found" }

And something like this for a “success”:

{ result: { name: "Bob", password: "oops, we shouldn't be sending this to the front end!" } }

In other words, successful calls populated the “result” field, and unsuccessful calls populated the “error” field. It was easy enough to check for the existence of an “error” or “result” on the front end, and proceed accordingly.

Returning a Result Object

The aforementioned LinkedIn post inspired me with an idea: if an API endpoint can return this type of response, why can’t a function? Couldn’t we have our functions return an object, like this:

type Error = object
  message: string

# The Nim language uses the [T] syntax for generics
type Result[T] = object
  value: ref T
  error: ref Error

# A Nim 'proc' is just a function, and the equals sign
# at the end of this line works like '=>' in other languages.
proc isSuccess[T] (this: Result[T]): bool =
  this.error == nil and this.value != nil

proc isFailure[T] (this: Result[T]): bool =
  not this.isSuccess

proc failure[T] (error: Error): Result[T] = 
  result.error = new Error 
  result.error[] = error

proc success[T] (value: T): Result[T] =
  result.value = new T
  result.value[] = value

… instead of throwing an exception? Then we could check the results, like this:

proc getUsernameFails (_: int): Result[string] =
  result = failure[string](Error(message: "User not found."))
proc getUsernameSucceeds (_: int): Result[string] =
  result = success[string](Error(message: "User not found."))
var usernameResult = getUserNameFails(999)
# [] is Nim's dereference operatorif usernameResult.isSuccess: echo usernameResult.value[]
else: echo usernameResult.error[]
usernameResult = getUserNameSucceeds(999)
if usernameResult.isSuccess: echo usernameResult.value[]
else: echo usernameResult.error[]

Of course, maybe you’ve already noticed the potential issue with this approach. Accessing the error field of a Success result, or the value field of a Failure result, without first checking for isSuccess or isFailure, will result in a null pointer exception (hardly ideal, considering the point of this exercise is to do away with exceptions!), or worse, a buffer overflow.

Discriminated Unions

Instead of a Regular old, Unassuming Result Objects (RUh-ROh’s…), the suggestion on LinkedIn involved a Union of two types. One type contains a value field, and the other contains an error field. The Union can exist in either a Success or a Failure state, and, depending on which state it’s in, it will contain one or the other field.

The Nim language accomplishes something similar by allowing you to use a case statement within an object declaration, to determine what fields the object should have. Think of it as syntatic sugar for the abstract factory pattern:

type ResultKind = enum  Success,
  Failure

type Result[T] = object
  # Nim infers from this case statement that a Result object has a  # ResultKind enum field, called 'kind'  case kind: ResultKind:
  of Success:
    # a Result object will only have this field when its kind field is set to ResultKind.Success
    value: T
  of Failure:
    # a Result object will only have this field when its kind field is set to ResultKind.Failure 
    error: Error 

Then, a function can indicate success or failure by returning one or the other variation of the Result object:

proc getUserById(id: int): Result[int] =
  if database.getUser(id) == nil:
    result.kind = Failure
    result.error = Error(message: "User not found")
  else:
    result.kind = Success
    result.value = user.id

However, there’s an issue with this approach, as well. It’s true that you’re no longer dealing with null pointer access when you try to inspect the value field of a Failure object, or the error field of a Success object. But the determination as to which fields of a given Result object are currently available can only be made at run-time; so, when the user tries to access a non-existent field, it throws an exception.

And so we’re back where we started.

Multiple Return Values

What we really want is a way to force the user to check the success status of a function call before trying to use the result. Go and React both come really close, by returning multiple values (Go), or an array (React). In Go, you do something like this:

error, result := myLib.DoAThing()
if !error {
  doSomethingWith(result)
}

While in React, you do something like this:

let [error, result] = doAThing()
if (error) console.log(error)
else doSomethingWith(result)

In Nim or Python, you can accomplish the same result using a tuple:

let (error, result) = doAThing()
if not error: doSomethingWith(result)
else: echo error

(Okay, so my example only works in Nim. But why would you use Python anymore, when you can get the same expressiveness from a language that also allows you to write compilers and OS kernels, and that also cross-compiles, at your option, to Javascript?)

A common complaint I see with this approach, especially in Go, where it is used most often, is that now you have to do an if !error check for nearly every line of code you write, which is tedious and ugly. To be fair, the LinkedIn solution – using discriminated unions – has the same issue.

… and that’s when I realized why Nim’s readLine function works the way it does. And was reminded of a best practice my dad (- another programmer -) told me about from back in the days before C++ and Java had unseated C as the only reasonable choice for Serious Programmers –

The Result Argument.

Nowadays, most languages default to pass-by-value arguments, like this:

let manipulateString = (str) => {
  str = str.toUpperCase()
}

let myString = "hello"
manipulateString(myString)
console.log(myString) // "hello"

You can try this in your browser’s developer tools. myString will remain unchanged, because the str argument contains a copy of the original value. Nim functions (known as “procs” – since Nim is a procedural language) work in a very similar way:

proc maniuplateString (str: string) =
  str = str.toUpperAscii() # does not compile - str is an immutable copy

let myString = "hello"
manipulateString(myString)
echo myString # hello

You can give manipulateString the power to modify str, however, by turning it into a mutable reference, using the var keyword:

proc maniuplateString (str: var string) =
  str = str.toUpperAscii()

var myString = "hello"
manipulateString(myString)
echo myString # HELLO

The ability to pass by reference means that we can store the result of a successful function call in its last argument, and use the function’s return value to indicate success or failure, like this:

proc getUserName (id: int, username: var string): bool =
  let user = database.getUserById(id)

  if user == nil: 
   return false
  else:
    username = user.username
    return true

var username # = ""

if not getUserName(999, username):
  return false # pass along the failure status

echo username

Nim auto-initializes all declared variables to sensible defaults; it also does not allow users to ignore a function’s return value – you have to explicitly discard it, with the self-documenting discard keyword:

var username
discard getUserName(999, username)
echo username # ""

This forces you to think about what you want to do with the true/false value returned by getUserName, and it forces you to think about what – if anything – is being stored in the username variable. If you absolutely refuse to think, and getUserName can’t get the username from the database, then we fall back on an empty string, which follows the principle of least surprise.

The real power of this convention manifests in a loop:

var input = ""
while stdin.readLine(input):
  doSomethingWith(input)

Your function can even use an enum value, to indicate exactly what went wrong:

type errno = enum
  # "errno" is a callback to that old best practice from C
  Success,
  InvalidUserId,
  UserNotFound,
  KeyCollision

proc getUserName(id: int, username: var string): errno =
  if id < 0: return InvalidUserId
  # ... etc. ...

I’m not sure how I feel about this way of doing things as opposed to exceptions. And I wish Nim had something like C++’ & operator to more clearly signal to the reader when we are and are not passing by reference. But I think I will try this way out for awhile in my side projects, and see how it holds up.

Leave a Comment