Errors Are Not Exceptions
Tags: languages, go, javascript
Published: 2021-01-29 15:18:16

---
title: Errors Are Not Exceptions
published: true
description: Many language ecosystems use try/catch paradigms to represent both errors and exceptions. This is wrong.
tags: languages, golang, javascript
slug: errors-not-exceptions
canonical_url: https://www.swyx.io/errors-not-exceptions
---

*[listen to me explain this in a podcast](https://share.transistor.fm/s/c10e417a)*

## TL;DR

- Errors are unrecoverable, Exceptions are routine. 
- Most languages (Java, PHP) build the distinction into the language. Yet others (Go) name them the other way round. Some languages (JavaScript, Python) treat them as synonyms. 
- No matter what way you name things, you should handle Errors and Exceptions separately in your code or bad things happen.

Because I started out in JS/Python and then went to Go, without touching Java, getting this distinction right took me a few hours of thinking and research. It's not self-evident!

## Context

If you've ever `throw`n an error in a function *expecting* its invoker to `catch` it, **you're doing it wrong**.

> Ok, I'll admit I'm just hamming up a mere opinion for a more eye-catching opening. But I do feel strongly about this so...

I was recently reminded of this while going through the Go FAQ and being reminded that [Go does not have exceptions](https://golang.org/doc/faq#exceptions).

*What?* If you've always coded in a language that has exceptions, this ought to jump out at you.

**Go does not have `try` or `catch`.** Despite those language constructs existing for decades, Go chose to have [Defer, Panic, and Recover](https://blog.golang.org/defer-panic-and-recover) instead. By convention and design, Go encodes an extremely strong opinion that **errors should be returned, not thrown**.

## But Why

Relying on *exception* handling to handle *errors* either leads to convoluted code or unhandled errors.

This kind of code is common in JavaScript:

```jsx
function trySomethingRisky(str) {
		if (!isValid(str)) throw new Error('invalid string!')
		return "success!"
}

function main() {
	try {
		return trySomethingRisky(prompt('enter valid name'))
	} catch (err) {
		if (err instanceof Error) {
			// handle exceptions
		} else {
			// handle errors
		}
	}
}
```

If you're thinking that you don't write this sort of code very often, you're probably not thinking through your failure modes enough. 

- JavaScript doesn't have a native way to indicate whether a function can throw, if you invoke it. So **you cannot lint against it** — you must either pay this cost earlier in manual code review or later in bug reports.
- An innocent `fs.readFileSync` call can bring down a whole server ([or memory-leak descriptors](https://github.com/mcollina/make-promises-safe)) given the wrong string.
- Promise calls without a `catch` in the browser will simply log silent errors (a terrible user experience).

The more function and module boundaries you cross, the more you need to think about defensively adding `try`/ `catch` and handling the gamut of errors that can happen, and the harder it is to trace where errors begin and where they are handled. 

> Aside - some authors like [this Redditor](https://www.reddit.com/r/golang/comments/9t7con/why_no_trycatch_in_golang_whats_the_theory_behind/e8ust3m/) and [Matt Warren](https://mattwarren.org/2016/12/20/Why-Exceptions-should-be-Exceptional/) make a performance driven argument for encouraging developers to not overuse exceptions. Exceptions involve a memory and compute intensive stack search. This matters at scale, but most of us will never run into this so I choose not to make a big deal out of it.

## Errors vs Exceptions

Let's attempt a definition:

- **Exceptions are expected failures**, which we should recover from.
- **Errors are unexpected failures**. By definition, we cannot recover elegantly from *unexpected* failures.

You might notice the ironic inversion - it is *errors* that are "exceptional", while *exceptions* are routine. This was very confusing to your humble author.

This is no doubt due to the fact that [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling#exception_handling_statements), [Python](https://docs.python.org/3/tutorial/errors.html), and other languages treat errors and exceptions as synonyms. So we throw `Error`s when we really mean to throw exceptions.

[PHP](https://www.guru99.com/error-handling-and-exceptions.html) and [Java](https://stackoverflow.com/questions/5813614/what-is-difference-between-errors-and-exceptions) seem to have this difference baked into the language. 

To make things extra confusing, Go uses `error` where other languages would call exceptions, and relies on `panic` to "throw" what other languages would call errors. 

> Notes: Chris Krycho observes that you can use Rust, F#, and Elm's `Result` in a similar way, and Haskell's `Either`.

## Exception Handling vs Error Checking

The realization that we need different paradigms for handling errors and exceptions is of course not new. [Wikipedia's entry on Exception Handling](https://en.wikipedia.org/wiki/Exception_handling#Criticism) quotes Tony Hoare ([creator of QuickSort, CSP and the null reference](https://en.wikipedia.org/wiki/Tony_Hoare)) saying that exception handling is "**dangerous. Do not allow this language in its present state to be used in applications where reliability is critical.**"

That was said in 1980, yet here we are 40 years later.

The alternative to exception handling is error checking. 

### Error Checking in Go

> Note: Go seems to have a strong opinion that "Errors" are routine and Exceptions (not formally named, but used in `panic`) are Exceptional - in direct opposition to other languages. I have opted to use Go-native terminology - minimizing confusion locally at the cost of increasing global confusion.

[Errors are values](https://blog.golang.org/errors-are-values) in Go — made to be passed, not thrown. Go's FAQ is worth quoting here:

*We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional.*

When something goes wrong, your default choice should be using multi-value returns to report errors:

```go
i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)
```

This pattern would be subject to the same weaknesses I outlined above, except for the fact that Go will refuse to compile if you 1) don't assign all returned values at the callsite or 2) don't use values that you assign. These two rules combined guide you to handle all errors explicitly near their origin.

Exceptions still have a place — but the language reminds you how rarely you should use it, by calling it `panic()`.  You can still `recover()` and treat it like a backdoor `try`/ `catch` in Go, but you will get judgy looks from all Gophers.

### Error Checking in Node

JavaScript lacks the 2 features I mention above to force you to handle errors.

To work around this and gently nudge you, Node uses [error-first callbacks](https://nodejs.org/api/errors.html#errors_error_first_callbacks):

```jsx
const fs = require('fs');

function errorFirstCallback(err, data) {
  if (err) {
    console.error('There was an error', err);
    return;
  }
  console.log(data);
}

fs.readFile('/some/file/that/does-not-exist', errorFirstCallback);
fs.readFile('/some/file/that/does-exist', errorFirstCallback);
```

This pattern is idiomatic in most Node libraries, but the further we get away from Node, the more we tend to forget that there is an alternative to throwing errors, when writing libraries and app code.

Lastly, it is tempting to `promisify` those callbacks:

```jsx
const util = require('util');
const fs = require('fs');

const stat = util.promisify(fs.stat); // i am using fs.stat here, but could be any error-first-callback userland function

// assuming top-level await
try {
	const stats = await stat('.')
	// do something with stats
} catch (err) {
	// handle errors
}
```

And we are right back where we started - being able to fling errors and exceptions arbitrarily high up and having to handle both in the same place.

## Other Reads

- [Dave Cheney on Go Errors and Exceptions](https://dave.cheney.net/2015/01/26/errors-and-exceptions-redux)
- Haskell Wiki on [Errors vs Exceptions](https://wiki.haskell.org/Error_vs._Exception)
- You can implement a `Result` or `Maybe` in JS: https://true-myth.js.org/

*Thanks to [Charlie You](https://twitter.com/charlieyouai) and [Robin Cussol](https://twitter.com/robincsl?lang=en) for reviewing drafts of this post.*