Go 2 Draft: Error Handling
Tags: go, errors
Published: 2018-08-29 16:14:31

---
title: "Go 2 Draft: Error Handling"
published: true
description: "Go 2 may be coming out within the next couple of years, let's take a look at the upcoming language changes!"
tags: Go, errors
---

# Go 2 is being drafted.

If you haven't [heard](https://www.youtube.com/watch?v=6wIP3rO6On8), there will be a few major language changes coming to `Go 2`. The Go team has recently submitted a [draft](https://go.googlesource.com/proposal/+/master/design/go2draft.md) of these changes, and as part of a 3-part series, I want to go through each part, explain it, and state my opinion on them! This is the first part of the series. After Error Handling, we will talk about Error Values, and then we can get to the big kahuna... Generics!

Remember that these are just drafts. They will most likely be added to the language, but there is still a chance that it won't. Also, remember that these drafts are not final, of course. The syntax will probably be a bit different from what it is right now.

These may not also be the only changes to the language, but these will probably be the only _major_ changes.

# So, what's the big deal with Error Handling?

Currently, if you want to check an error, you need to do `if err != nil { ...`. The construct is nice, but when you're working with any kind of I/O, your code starts to look something like this... (using the example from the [draft])

```go
// From the draft
func CopyFile(src, dst string) error {
	r, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}
	defer r.Close()

	w, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}

	if _, err := io.Copy(w, r); err != nil {
		w.Close()
		os.Remove(dst)
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}

	if err := w.Close(); err != nil {
		os.Remove(dst)
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}
}
```

We had to write `fmt.Errorf("copy %s %s: %v", src, dst, err)` three times, and `w.Close()` and `os.Remove(dst)` twice... and this is a pretty small function. So how can we fix this?

# Introducing: `check` and `handle`!

`check` and `handle` will be new keywords, and you can kind-of think of them as a `panic-defer-recover`, but for use within a single function.

The format for `check` is `check <expression>`, where `<expression>` is either an error or a function call, where the last return value is an error. These are also expressions themselves, if the error is `nil`, then it returns all of the other arguments (ex: `f := check os.Open(fileName)`).

If the error value is not nil, then `check` will jump to the `handle` blocks. To see what I mean, let's look at a simple example.

Go 1:
```go
type Parsed struct { ... }

func ParseJson(name string) (Parsed, error) {

	// Open the file
	f, err := os.Open(name)
	if err != nil {
		return fmt.Errorf("parsing json: %s %v", name, err)
	}
	defer f.Close()
    
	// Parse json into p
	var p Parsed
	err = json.NewDecoder(f).Decode(&p)
	if err != nil {
		return fmt.Errorf("parsing json: %s %v", name, err)
	}

	return p
}
```
Okay, so let's simplify this with `check` and `handle`!

```go
type Parsed struct { ... }

func ParseJson(name string) (Parsed, error) {
	handle err {
		return fmt.Errorf("parsing json: %s %v", name, err)
	}

	// Open the file
	f := check os.Open(name)
	defer f.Close()

	// Parse json into p
	var p Parsed
	check json.NewDecoder(f).Decode(&p)

	return p
}
```
How about that? We've trimmed down a lot of our boilerplate!

But what about that example from earlier... It had a lot more error handling! We had to clean up our files if they didn't copy properly... So how do we handle that? Let's take a look at the [draft]!

```go
// From the draft
func CopyFile(src, dst string) error {
	handle err {
		return fmt.Errorf("copy %s %s: %v", src, dst, err)
	}

	r := check os.Open(src)
	defer r.Close()

	w := check os.Create(dst)
	handle err {
		w.Close()
		os.Remove(dst) // (only if a check fails)
	}

	check io.Copy(w, r)
	check w.Close()
	return nil
}
```
If you have multiple `handle` blocks, they will be executed from the bottom-most one to the top-most. This allows you to have more clean-up when an error occurs as a function flows.

Of course, this construct also works in functions which do not return errors, as noted in the [draft]!

```go
// From the draft
func main() {
	handle err {
		log.Fatal(err)
	}

	hex := check ioutil.ReadAll(os.Stdin)
	data := check parseHexdump(string(hex))
	os.Stdout.Write(data)
}
```

# What do I think?

I think this is a really good change! My only real issue with it is that now `check` and `handle` become keywords, which are both variable names that aren't _uncommon_. I used to like the idea of `collect` (as illustrated in my [previous blog post](https://dev.to/deanveloper/my-favorite-go-2-proposals-3lie)), although I think my view on `collect` changed after I wrote it. It just seemed like `try-catch` and didn't have a very intuitive syntax.

`check-handle` is still quite a bit like `try-catch`, although it is much more powerful. It allows more clean-up as the function progresses without adding more indentation. Every error check is also explicit, so you know where the exit points of the function are at a very quick glance.

The [draft] also contains a lot of details about what inspired this decision! They took a lot of inspiration from both Rust and Swift, which both have errors that must be checked explicitly (unlike Java, C++, Python, etc. which have implicit unwrapping of the stack).

This change also works really well with [the new `error` idioms](https://go.googlesource.com/proposal/+/master/design/go2draft-error-values-overview.md)... but that's for the next post in the series!

[draft]: https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md