In the first edition, we discussed Go packaging. There’s more to cover there, but we learned about the main package and how to import other packages: that’s all we need for the moment.
In this edition, we’re going to cover functions: from the basics to some of the more complex concepts.
By the end of it, you should have a good grasp of functions in Go.
So what is a function?
A function is a piece of code that may return a value, it may accept and use arguments (values) too.
It can do both of these things or neither.
Sometimes a function is just a body of code that we’d like an easy way to reuse. Functions allow us to do this, as well as separate tasks and generally structure our code better.
Most languages implement functions, but Go has some special features that make them more powerful, and more flexible.
Before we get to these features, we’ll cover the basics first.
A simple Go function
In Go, a function is defined with the func keyword. The name of the function follows this.
Parameters which the function accepts, together with their types are shown in the brackets that follow this with the return type following this. Collectively we call this the function’s signature. Finally, the function code itself is written between curly braces.
A simple function might look like this:
func SayHello(name string) string {
return fmt.Sprintf("Hello %s", name)
}
All this function does is accept the parameter “name” which is of type string, pre-pend “Hello ” to it and return it, again as type string.
The function uses the fmt package, so you need to import this. We covered packages and imports in the last tutorial, so for brevity, that’s been omitted here.
We might use it like this:
var greeting = SayHello("GolangAtSpeed")
fmt.Println(greeting)
Here “greeting” is a variable, we haven’t covered variables yet, but just know, greeting stores the return value of the SayHello function.
We could expect the output of this code to print this:
Hello GolangAtSApeed
Easy yes? Nothing revolutionary here, if you’ve coded in any language you’ll note the similarities.
But Go offers us much more, and we’ll deep-dive functions next.
Multiple parameters of the same type
If a function accepts multiple parameters of the same type, its signature can be simplified a little by grouping those parameters together.
In the next two examples, the function bodies are omitted as they are not needed to illustrate the point.
Compare this first signature:
func MakeAddress(name string, active bool, address string){
…
}
With this signature:
func MakeAddress2(name, address string, active bool) {
…
}
Can you see the difference?
It’s a subtle difference when there are only three parameters, but you can see how the grouping allows us to simplify the signature. This can help make large function signatures easier to read.
Exported or Unexported?
If we were developing a package, and we included the function SayHello() in the package, users could call that function from outside of the package.
But, what if we wanted it to be a private function, that could only be called by other functions from within the package? We need to do this sometimes.
Many languages adopt syntax that explicitly states if a function (or variable) is private or public, exported or unexported.
Go does not use these keywords. Instead, it communicates this semantically.
If we don’t want the function to be accessible outside of the package, we name it with a lowercase letter as the first character.
So instead of SayHello(), we’d name the function sayHello().
Do you like that? I love it myself. It’s simple, you get used to it quickly and we don’t have to remember any extra keywords!
This semantic naming applies beyond functions too, to variables and more.
Multiple return values
Go lets us define more than one return value in the function signature. This is useful, especially in functions that set things up.
Without multiple return values, we might resort to using global state which is often a source of problems in programs.
You’ll commonly see two return values in the function signature, the value you want to access, and additionally an error type return, or in some cases a boolean type return.
The former provides information about any errors the function might have encountered, the latter whether an operation was successful (true) or not (false).
In all cases, multiple returns are placed in the same part of the function as a single return value - but in brackets - and the return statement includes all variables to return.
Here is an example:
func IntWithError(num int) (int, error){
…
return myInt, err
}
This feature is powerful but open to abuse. If you need, say, more than 3 return values, there may be better ways to write the code. But this is fine, you can change (refactor) it later.
Variadic functions
In Go a variadic function allows multiple arguments to be passed for a single parameter.
The concept seems a bit alien at first sight, so let’s take a simple example from the standard library. We’re going to look at the Println function from the fmt package since we’ve used this already.
This is the function signature for Println taken from the documentation for the package:
func Println(a ...interface{}) (n int, err error)
Notice anything unusual? Well, you probably see the keyword interface and think what’s an interface - we’ll get to this, but not today.
Notice, the three dots that precede the word interface? That’s an ellipsis, and that means this function is variadic, as indeed are many of the functions in the fmt package.
So we can pass multiple arguments for the same property, which means we can do stuff like this:
fmt.Println(“this”, “is”, “a”, “variadic”, “function”)
And get printed output like this:
this is a variadic function
We’re not going to cover what happens under the hood here, not yet anyway because for that we need to understand something called a “slice”. And we’re not there yet.
For now, just know that Go has variadic functions and the ellipsis is the way to recognise them from the function signature.
We can also do this with the return value of a function, but again we need a slice so we’ll park variadic functions here.
Named return values
Named returns allow function signatures to be even more descriptive. Using them tells us more about the intention of each return and allows us to reduce the code within the function body somewhat.
Take this function:
func makeDogInfo(ownerName, dogName string, dogAge int) (string, string, int) {
var ownerGreeting, dogIntro string
var dogHumanAge int
ownerGreeting = “hello ” + ownerName
dogIntro = “your dog” + dogName + "is”
dogHumanAge = dogAge * 7
return ownerGreeting, dogIntro, dogHumanAge
}
It’s not possible to know what the three return types do (string, string, int) from the function signature alone.
For this information, we need to explore the function body and check which variables are returned for each type.
We can see we return ownerGreeting, dogIntro, and dogHumanAge. We now have more context about what the function gives us back.
Now, consider this example, which uses named returns:
func makeDogInfo(ownerName, dogName string, dogAge int) (ownerGreeting, dogIntro string, dogHumanAge int) {
ownerGreeting = “hello ” + ownerName
dogIntro = “your dog” + dogName + "is”
dogHumanAge = dogAge * 7
return
}
Can you see the difference? The function signature now names the return values, so our understanding of what the function is giving us is much clearer - without looking at the function body!
But now look at the function body. Not only do we NOT need to explicitly declare our variables in the function body, but we also do not need to explicitly return the variables either. There’s less code to read, and write!
Both operations are implicit since named returns values give the compiler all the information it needs.
Note also the type grouping on the named returns ;)
Receivers
Receivers are a special kind of function that we can attach to any Go type. Commonly you will see receivers attached to a struct type. We refer to them as receivers since they “receive” the value (or properties) of the type they are attached to.
We will cover receivers and types in a future tutorial too.
Anonymous Functions & Closures
In Go, functions are first-class citizens. This means they are a Go type, so can be passed as arguments to other functions, and be returned from other functions too.
These functions are often anonymous functions. An anonymous function, or function literal is a function that has no name.
Anonymous functions & Closures are not unique to Go, so I’m going to do no more than mention them at this point. I’ll highlight them, if and when, we encounter them in any future tutorials.
Summary
Wow, that was longer than I thought it would be!
Hopefully, you found my article helpful, and you now know about all the ways Go allows us to use functions. A few bits we skirted over will make more sense later for sure.
If you enjoyed, please share it with your friends and colleagues and encourage them to subscribe - I publish one Golang “learning” newsletter a week, and occasionally another with a few interesting links I’ve found.
I promise I won’t spam you!
As always, get involved in the comments with your feedback and observations.
Until next week!