A Closer Look At Functions in Go

Share this article

Functions are one of the basic building blocks of any programming language. However, the feature set and role they play differ quite a lot from language to language. In this article, we’ll have a look at what Go has to offer when it comes to functions. We are going to discover that functions are first-class citizens in Go and provide the developer a very rich feature set – with some interesting details that are not apparent at first. Let’s get started by covering the basics.

Arguments

A function in Go can take any number of typed arguments. All arguments are required, as there is no concept of optional arguments. Important to know as well is that arguments are not passed by reference, but copied into the function (with a few notable exceptions which we’ll get to shortly). This means that if the argument is modified inside the function, it won’t have any effect on the original value. The only way to circumvent this is to pass a pointer to the function. If you’re coming from dynamic languages like PHP, Ruby or JavaScript, then the concept of pointers might not be familiar to you. However, you would have used pointers implicitly as these languages pass (some) arguments by reference. The basic concept of a pointer is easy: when you declare a variable in your program, it lives at some location in memory. Pointers simply contain the address of that location (they “point” to the variable). That means that through a pointer, you can get the actual value and modify it.

Now, back to functions. As mentioned earlier, most arguments are copied into functions. If a pointer is copied, it still points to the same address in memory, so you can modify the value. A simple example:

[golang] var counter int func increment(i int) { i = i + 1 } counter = 0 increment(counter) fmt.Printf(“%v”, counter) // Prints 0 [/golang]

vs.

[golang] var counter int func increment(i *int) { *i = *i + 1 } counter = 0 increment(&counter) fmt.Printf(“%v”, counter) // Prints 1 [/golang]

& creates a pointer, and * dereferences it (returns the value the pointer points to).

Remember I mentioned that almost everything is passed by value? It is important to know that maps, slices and channels are different, which can be quite confusing at first. The Golang FAQ has a detailed explanation of the reasoning behind this design decision. Another thing that receivers or arguments which have an interface type are not prepended by a *, but they still might be pointers!

Return values

In Go, functions can also have return values. If none is specified, the function returns nothing (as opposed to other languages where you might get nil or undefined which is actually something). In fact, trying to use the result of a function that has no return value is a compile error.

A more interesting characteristic is that Go allows for multiple return values. This is something most popular web programming languages lack, and it turns out to be very handy. Inside Go’s standard library it is commonly used for error handling. Consider the Writer interface:

[golang] type Writer interface { Write(p []byte) (n int, err error) } [/golang]

If Write is successful, n will be the number of bytes written and err will be nil. In the case writing failed, n will be 0 and err will hold the occured error. Consumer code can use this in the following fashion:

[golang] if n, err := file.Write(p); err != nil { fmt.Println(“Could not write.”) } [/golang]

The Writer interface shows another interesting bit about return values: they can be named in advance. This removes the need to initialize the variables inside the function. These named variables get automatically initialized to their zero-value and can be returned at any time in the function by a simple return statement.

Receivers

Up to here, we have discussed regular functions. If you’re familiar with object oriented languages, you will know that they have a concept similar to a function called a method, which is basically a function, but acts in the context of an object.

Go provides this functionality through receivers. However, there is a slight difference because anything can be a receiver. It could be a struct, but it can be just an integer as well. Also, the receiver might be a value or a pointer.

Take for example this method Name defined on a value of type User (which is a struct):

[golang] type User struct { firstname string lastname string } func (user User) Name() string { return user.firstname + ” ” + user.lastname } peter := User{firstname: “Peter”, lastname: “Pan”} fmt.Println(peter.Name()) // prints “Peter Pan” [/golang]

Here, whenever Name() is called, the User struct is copied. Often, it is better to use a pointer receiver. Also, it is necessary to do so if you want to modify the receiver, for example like this:

[golang] func (user *User) SwapName() { firstname := user.firstname user.firstname = user.lastname user.lastname = firstname } paul := &User{firstname: “Paul”, lastname: “Panther”} paul.SwapName() fmt.Println(paul.Name()) // prints “Panther Paul” [/golang]

If this method were defined on the receiver user User, the name would only be swapped inside the function, and fmt.Println would have printed Paul Panther.

What would happen if we tried to call use peter.SwapName? peter is of type User (a value), the receiver however is of type *User (a pointer). Does this work? Interestingly, it does. Go is smart enough to create and pass a pointer to the User value, and then the function operates on that pointer, modifying the peter variable.

The dreaded null pointer

Another question you might have asked yourself while reading about receivers is what would happen if the receiver were nil? This is a common problem in many languages and I’m sure you have tried to call a method on an object that was nil at some point in your life as a programmer, and the result was not good at all. You then went forth and added safeguards to your code everywhere those null pointers might occur and ended up with very ugly code …

Go to the rescue! Let’s say we have a FirstUser() method which returns our first user. However, we don’t have any users yet, so the initial implementation will be:

[golang] func FirstUser() *User { return nil } [/golang]

Now, if we want to get the name of the first user in our code, we would write something like this:

[golang] name := FirstUser().Name() [/golang]

With code like this and because FirstUser() returns nil for now, Go will panic. The easiest way to fix this is to add a safeguard as mentioned above, but it’s not looking very pretty:

[golang] user := FirstUser() if user != nil { name:= user.Name() } [/golang]

In Go we can deal with this in a better way: inside the functions. We can do this because in Go, nils can be typed:

[golang] func (u *User) Name() string { if u == nil { return “” } return user.firstname + ” ” + user.lastname } [/golang]

In contrast to other languages, where you can’t call any method on nil objects, Go lets you call the function on the pointer receiver. So if there might be a scenario in which the receiver is nil, make sure to handle it inside the function so you don’t need the safeguard outside, making consumer code a lot less cluttered.

Closures

One final characteristic of Go functions is that they can be anonymous and used as closures, very similar to what you might know from JavaScript.

For example:

[golang] func counter() func() int { c := 0 return func() int { c += 1 return c } } [/golang]

The counter function has a return type of func() int, which means it returns a function with a return type of int. If we call the counter function and store its return value in a variable, we can call that variable (which holds a function value) several times and the counter will be increased (as the inner function has access to the same c variable):

[golang] count := counter() fmt.Println(count()) // Prints 1 fmt.Println(count()) // Prints 2 fmt.Println(count()) // Prints 3 [/golang]

Conclusion

Functions are a major building block of the Go language. They have some unusual features such as multiple return values and pass-by-copy which have a strong influence on the way programs are written. The implementation of “methods” through receivers is quite flexible and allows for more than just typical methods, such as nice handling of nils. However, Go functions also require the programmer to have a thorough understanding of values vs. pointers, and when to use which.

Frequently Asked Questions (FAQs) about Functions in Go

How can I return multiple values from a function in Go?

In Go, a function can return multiple values, which is a unique feature not found in many other languages. To return multiple values, you simply declare them in your function signature. For example, a function that returns an integer and a string would look like this:

func myFunction() (int, string) {
return 42, "hello"
}
You can then assign these return values to variables when you call the function:

num, str := myFunction()
In this case, num would be 42 and str would be "hello".

What is a variadic function in Go and how do I use it?

A variadic function is a function that can take an indefinite number of arguments. In Go, you can create a variadic function by using an ellipsis ... before the type of the last parameter. Here’s an example of a variadic function that takes an arbitrary number of integers and returns their sum:

func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
You can call this function with any number of integer arguments:

sum(1, 2, 3, 4) // returns 10
sum(5, 6) // returns 11

How do I define a function inside another function in Go?

Go supports anonymous functions, which can be defined inside another function. These are also known as closure functions. Here’s an example:

func outerFunction() func() int {
x := 0
return func() int {
x++
return x
}
}
In this example, outerFunction returns an anonymous function that increments x each time it’s called. The anonymous function retains access to the x variable from its outer scope.

How do I pass a function as a parameter in Go?

In Go, functions are first-class citizens, which means they can be passed as arguments to other functions. Here’s an example:

func applyFunc(f func(int) int, x int) int {
return f(x)
}

func double(x int) int {
return x * 2
}

func main() {
fmt.Println(applyFunc(double, 5)) // prints 10
}
In this example, the applyFunc function takes a function f and an integer x as arguments, and applies f to x.

How do I use recursion in Go?

Recursion is a concept where a function calls itself. It’s often used to solve problems that can be broken down into smaller, similar problems. Here’s an example of a recursive function in Go that calculates the factorial of a number:

func factorial(n int) int {
if n == 0 {
return 1
}
return n * factorial(n-1)
}
In this example, the factorial function calls itself with n-1 until n is 0, at which point it returns 1.

What is a function signature in Go?

A function signature in Go defines the function’s input parameters and return types. It’s the part of the function declaration that comes after the func keyword and before the function’s body. Here’s an example:

func add(x int, y int) int {
return x + y
}
In this example, the function signature is add(x int, y int) int. It indicates that the function takes two integers as input and returns an integer.

How do I create a higher-order function in Go?

A higher-order function is a function that takes one or more functions as arguments, returns a function, or both. Here’s an example of a higher-order function in Go:

func applyFunc(f func(int) int, x int) int {
return f(x)
}
In this example, applyFunc is a higher-order function because it takes a function f as an argument.

How do I use default parameters in Go?

Go does not support default parameters in the same way as some other languages like Python or JavaScript. However, you can achieve similar functionality by using variadic functions or by passing a struct that contains all the parameters to the function. Here’s an example using a struct:

type Params struct {
X, Y int
}

func add(p Params) int {
return p.X + p.Y
}

func main() {
fmt.Println(add(Params{X: 1, Y: 2})) // prints 3
fmt.Println(add(Params{X: 1})) // prints 1
}
In this example, if you don’t provide a value for Y, it defaults to 0.

How do I use function literals in Go?

A function literal, also known as an anonymous function or a lambda, is a function that is defined without a name. Here’s an example:

f := func(x, y int) int {
return x + y
}
fmt.Println(f(1, 2)) // prints 3
In this example, f is a function literal that takes two integers and returns their sum.

How do I use defer in Go?

The defer keyword in Go allows you to schedule a function call to be executed after the function completes. It’s often used for cleanup tasks like closing files. Here’s an example:

func main() {
f, _ := os.Open("file.txt")
defer f.Close()

// do something with f
}
In this example, f.Close() will be called after the main function completes, regardless of where the main function returns.

Michael SauterMichael Sauter
View Author

Michael Sauter is a German web developer at SitePoint. He's maintaining the backend of the various SitePoint sites and works on new features and products. Usually working with Ruby or PHP - currently interested in Go and Docker.

functionsgogolangmethods
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week