This lesson is locked. Login or Subscribe for more access!

Why you should use a run func

Duration: 3 mins

Instead of putting all your startup logic in main.go, consider a run function. Let's see why.

Instructor

Matt Boyle

Share with a friend!

Transcript

After writing Go for long enough, you'll become very familiar with main functions that look like this.

Where we call a function, get back an error, check to see if it's OK or not, and log.Fatal if it's not. We might get some other thing, do something with it. There's nothing necessarily wrong with this, but one challenge is that it's not very readable. There's a lot going on, and you have to walk through it to figure out everything that's happening. There's no sort of error propagation; we're just calling log.Fatal on everything.

It's also hard to reason about adding more things to it, and it's not very testable. While you can test the main function, it's quite challenging to do so. So one pattern that people have started to adopt as a community is creating a separate function called run. For now, I'm going to do it like this, and we'll talk about some improvements we can make in a moment.

Effectively, what we do is move all of this bootstrapping logic into the run function and return errors instead. Then our main function can simply be called like this. So this probably doesn't look too different for now, but one thing we could do is start to propagate errors and add context. One improvement we might make is something like this: "do a thing" — errored. We've now given a little more logic to our function here as to what failed.

Also notice I exported this. So this is really testable now. I can import this anywhere and test it, which is great. Another improvement people like to make is to pass a context into this function too.

Now, this isn't super useful in this example, but in future examples—like with an HTTP server or clients—this means you can propagate a context all the way through as well, which is just really neat and looks much simpler to use. The main advantage of this approach is that all the logic is encapsulated inside the run function. We've now centralized our error-handling concerns, and this is much simpler to test.

One final thing I'll call out is that this pattern works especially well if you're passing arguments from standard input, because now they become very easy to test in a unit test as well. So I hope you found this useful. You'll see this pattern quite widely used within Go, so I thought it was worth teaching you about. Let's go.