When Should I Use One Liner if...else Statements in Go?
Tags: go, errors, beginners
Published: 2018-03-06 18:54:49

---
title: When Should I Use One Liner if...else Statements in Go?
published: true
description: After using Go for a few weeks, chances are you are going to run across a single-line if...else statement. Most often, you will see this with an err (eg if err := doStuff(); err != nil { ... }). This article discusses the situations where a one-liner is appropriate, and ones where it isn't a great fit.
tags: golang, go, errors, beginner
canonical_url: https://www.calhoun.io/one-liner-if-statements-with-errors/
---

> *This article was original posted at [calhoun.io](https://www.calhoun.io/one-liner-if-statements-with-errors/)*

After using Go for a few weeks, chances are you are going to run across a single-line `if...else` statement. Most often than not, the first one you see will be used for error handling and will look something like this:

```go
if err := doStuff(); err != nil {
  // handle the error here
}
```

The first bit of the `if` is known as an [initialization statement](https://golang.org/doc/effective_go.html#if), and it can be used to setup local variables. For functions that only return an error, the initialization statement is an incredibly useful tool. The error is scoped only to the `if` block that handles it, and our code ends up being a little easier to read.

But when is it appropriate to use this approach rather than breaking your code into multiple lines? Are there any rules or guidelines for when you should or shouldn't use the one-liner `if` statement?

In this post we are going to explore situations where using an initialization statement is appropriate, ones where it isn't, and explain some of the subtle differences between the two.

## Break up long expressions

This first section might be a bit controversial. Everyone has a different opinion on whether we should break long lines into multiple lines, what the max character count is, and everything else in between. The [Go Wiki](https://github.com/golang/go/wiki/CodeReviewComments#line-length) has a section that discusses this topic, and I generally agree with it, so I suggest you check it out. I'll wait...

Okay, back from reading? Great! Now let's look at some examples.

First up we have the case of the long function name. The best solution to this is to rename the function, but you may be using another library where that isn't possible. Let's face it, every once in a while a Java developer gets his hands on some Go code and we can't spend all our time cleaning up after them.

```go
// rather ugly
if err := thisHasAReallyLongSuperDescriptiveNameForSomeCrazyReason(); err != nil {
	// handle the error
}

// better
err := thisHasAReallyLongSuperDescriptiveNameForSomeCrazyReason()
if err != nil {
	// handle the error
}
```

*Run it on the Go Playground → <https://play.golang.org/p/gxLEZU_Uhmw>*

Assuming we can't rename the function, I tend to place these calls on a single line. Again, that isn't a set-in-stone rule, but it is rare to find a situation where using a few lines isn't clearer than putting that monster of a function call on a line with other logic.

Another situation where you might encounter extra long `if` statements are functions that accept many arguments; this is especially true if you are passing in hard-coded strings.

Once again this can be solved in a variety of ways, such as breaking the arguments onto new lines and not using the one-liner syntax. A few examples are shown below, and we will discuss them a bit more after you see the code.


```go
// pretty ugly
if err := manyArgs("This is a fairly long message about doing some stuff", "and we might provide even more data"); err != nil {
	panic(err)
}

// Fix 1: better, but the condition of the if statement is easy to miss
if err := manyArgs(
	"This is a fairly long message about doing some stuff",
	"and we might provide even more data"); err != nil {
	panic(err)
}

// Fix 2: probably the best solution if using the "one-liner" format
if err := manyArgs(
	"This is a fairly long message about doing some stuff",
	"and we might provide even more data",
); err != nil {
	panic(err)
}

// Fix 3: my personal preference here is not using the "one-liner" format
err := manyArgs(
	"This is a fairly long message about doing some stuff",
	"and we might provide even more data")
if err != nil {
	panic(err)
}
```

*Run it on the Go Playground → <https://play.golang.org/p/bB1lvtn5dpJ>*

In Fix 1 it is a little more clear what is going on, but I dislike it because the condition - `err != nil` - is easy to lose in the rest of the code.

Fix 2 is a big improvement. It is easy to forget about the comma after the second argument, but the upside is once you add the comma you can add new arguments to the function call without needing to change the existing code. This is pretty handy when calling variadic functions and adding another piece of data to it. Another perk here is that the condition is on its own line, making it much easier to read when just glancing at the code.

Finally we have my personal favorite, Fix 3; this is similar to the fix we used before - we make the function call on its own line, then create the `if` block afterwards using that data. This just reads cleaner to me because the `if` term is closer to where it is relevant - the condition clause. Adding some distance between the clause and the `if` keyword doesn't bug me when it is a true one-liner, but on multi-liners like this it just doesn't feel as readable. YMMV.

## Variable scopes

We've seen a few reasons to break up our `if` statements, but we also need to discuss the ramifications of breaking the initialization clause out of the `if` statement. That is, we need to discuss the scope of variables.

When we declare a new variable in the initialization portion of an if statement, the variable is declared within the scope of the `if` statement. This means that it is only available during the lifetime of that `if` statement. An example of this shown below.

```go
func main() {
	if x, err := demo(); err != nil {
		// handle the error
	}
	// this doesn't work because x is scoped to the if block
	fmt.Println(x)
}

func demo(args ...string) (int, error) {
	return 1, nil
}
```

*Run it on the Go Playground → <https://play.golang.org/p/jj9MDxYINFh>*

If we want to access the `x` variable in the previous example, we need to declare it outside the `if` statement otherwise it goes away once the if statement is completed. This is also why we can have an existing variable named `err` and then redeclare it inside of an if statement. The new variable is in a new scope, so it is permitted by the compiler.

```go
func main() {
	var err error
	if err := demo(); err != nil {
		fmt.Println("Error in if block:", err)
	}
	fmt.Println(err) // this will be nil still!
}

func demo(args ...string) error {
	return errors.New("on no!")
}
```


*Run it on the Go Playground → <https://play.golang.org/p/tASeTl2tlmK>*

**Warning:** *Scoped variables with the same name as variables in an outer scope can lead to incredibly confusing bugs at times. Using them with `if` blocks is typically fine, but they can be especially problematic when you have package-global variables and create a new, scoped variable inside of a function with the same name. `:=` is a helpful shorthand for declaring new variables, but it is important to understand how scoping works when using it.*


Scoped variables can also be accessed inside of `else` blocks of an if statement. This can be illustrated by modifying our previous example with the `x` variable.

```go
if x, err := demo(); err != nil {
	// handle the error
} else {
	fmt.Println(x)
}
```

*Run it on the Go Playground → <https://play.golang.org/p/uRfCxR9bLSP>*

While this is perfectly valid code, it can easily lead to Go code that isn't very idiomatic. According to [Effective Go](https://golang.org/doc/effective_go.html#if)...

> In the Go libraries, you'll find that when an `if` statement doesn't flow into the next statement — that is, the body ends in `break`, `continue`, `goto`, or `return` — the unnecessary `else` is omitted.

It is fairly common for an `if` statement that checks for an error to return as part of handling the error. That is, if we added error handling to our previous code it is likely to look something like this:

```go
if x, err := demo(); err != nil {
	return err
} else {
	fmt.Println(x)
}
```

We might return the error, panic with it, log it and then call `os.Exit`, or if in a `for` loop we might `continue`, but in all of those scenarios we aren't flowing into the next statement in our code. In these situations it makes more sense to declare our variables and call the `demo` function outside of the `if` scope, and then use them in the if statement. Then our code becomes:

```go
x, err := demo()
if err != nil {
	return err
}
fmt.Println(x)
```

While effectively the same, this code is typically easier to read and makes it much easier to continue using `x` if we need it later in our function.

## Potential annoyances

Now that we understand how scoping works, we need to look at one more potential nuisance that can occur when *NOT* using one-liner if statements - undefined or already defined variables.

Imagine you had the following code:

```go
err := demo1()
if err != nil {
	panic(err)
}
err = demo2()
if err != nil {
	panic(err)
}
err = demo3()
if err != nil {
	panic(err)
}
```

*Run it on the Go Playground → <https://play.golang.org/p/UZtbUjmv1e5>*


In the first line we declare the `err` variable, and then when we call `demo2` and `demo3` the variable is already declared, so we use `=` instead of `:=`, which would redeclare the variable.

This code is perfectly valid, but what would happen if we were working on our code and needed to add another function call *before* the call to `demo1`? Perhaps we need to call `demo2` beforehand.

Well, chances are we would add something like the following code to the top of our function.

```go
err := demo2()
if err != nil {
	panic(err)
}
```

*Run it on the Go Playground → <https://play.golang.org/p/tqMQDQ_gmTe>*

We need to declare `err` with this function call, because it hasn't been declared yet. Remember, we are adding this *before* the call to `demo1` in our original code, but we now have two declarations of `err` inside the same scope; this will cause our compiler to complain and our code won't compile.

```
prog.go:8:6: no new variables on left side of :=
```

To fix this, we need to go edit the line where we call `demo1` and update that code to use `=` instead of `:=`.

```go
err := demo2()
if err != nil {
	panic(err)
}
err = demo1()
if err != nil {
	panic(err)
}
// ... unchanged
```

*Run it on the Go Playground → <https://play.golang.org/p/3vE1FnA763a>*

A similar annoyance can occur when you delete existing code. For instance, if we were to no longer need the first function call - currently a call to `demo2` - and we removed it from our code, we would need to either manually declare the `err` variable, or go update the very next function call that uses it to use `:=` instead of `=`.

```go
var err error
err = demo1()
if err != nil {
	panic(err)
}
err = demo2()
if err != nil {
	panic(err)
}
// ... unchanged
```

*Run it on the Go Playground → <https://play.golang.org/p/MkTkEZoUlql>*

If you expect to be editing code enough that using `:=` will be a nuisance, consider simply declaring the variable ahead of time in your function (like we did in the last example). This is especially common with errors, as you almost always need a variable to capture errors and it isn't uncommon to need to add new code before the first time you declare it.

Sometimes `if` scoped variables can also help reduce the frequency with which this issue comes up, but as we saw earlier we can't always use these without sacrificing readability to some degree. Unfortunately, there isn't a set of rules we can follow to determine what the best approach is at any time; it ends up being a judgement call. But don't worry - as you get more familiar with Go you will start to gain some intuition that helps you make decisions like this. Until then, just keep practicing!

**Looking for practice projects to code in Go?**

I am releasing new exercises for my FREE course, [Gophercises](https://gophercises.com), almost every week. In each exercise you are presented with a relatively small project that should take you anywhere from an hour to a few hours to complete. Once you take a stab at the exercise you can watch screencast videos where I code a solution and explain what I'm doing as I write my code.

Whether you solve the problem on your own or just want to watch me code a solution, each exercises is curated to teach you something unique. Some are designed to teach you about a new library, others to introduce new coding techniques, and some focus on broader subjects like writing and benchmarking concurrent code.