The Zoo of Go Functions
An overview of anonymous, higher-order, closures, concurrent, deferred, and other kinds of Golang funcs.

This post is a summary for the different kind of funcs in Go. I’ll go into more detail in the upcoming posts because they deserve more. This is just a start.

Named Funcs
A named func has a name and declared at the package-level — outside of the body of another func.
👉 I already have explained them fully in my another post, here.


Variadic funcs
Variadic funcs can accept an optional number of input parameters.
👉 To learn more about them check out my other post, here.


Methods
When you attach a func to a type the func becomes a method of that type. So, it can be called through that type. Go will pass the type (the receiver) to the method when it’s called.

Example
Create a new counter type and attach a method to it:
type Count intfunc (c Count) Incr() int {
c = c + 1
return int(c)
}
The method above is similar to this func:
func Incr(c Count) int


Value receiver
The value of the Count instance is copied and passed to the method when it’s called.
var c Count; c.Incr(); c.Incr()// output: 1 1
It doesn’t increase because “c” is a value-receiver.


Pointer receiver
To increase the counter’s value you need to attach the Incr func to the Count pointer type — *Count
.
func (c *Count) Incr() int {
*c = *c + 1
return int(*c)
}var c Countc.Incr(); c.Incr()// output: 1 2



There are more examples from my previous posts: here and here.

Interface methods
Let’s recreate the above program using the interface methods. Let’s create a new interface named as Counter:
type Counter interface {
Incr() int
}
onApiHit func below can use any type which has an Incr() int
method:
func onApiHit(c Counter) {
c.Incr()
}
Just use our dummy counter for now — you can use a real api counter as well:
dummyCounter := Count(0)onApiHit(&dummyCounter)// dummyCounter = 1

Because the Count type has the Incr() int
method on its method list, onApiHit func
can use it to increase the counter — I passed a pointer of dummyCounter to onApiHit, otherwise, it wouldn’t increase the counter.


The difference between the interface methods and the ordinary methods is that the interfaces are much more flexible and loosely-coupled. You can switch to different implementations across packages without changing any code inside onApiHit etc.

First-class funcs
First-class means that funcs are value objects just like any other values which can be stored and passed around.


Example:
The sample program here processes a sequence of numbers by using a slice of Crunchers as an input param value to a func named “crunch”.

Declare a new “user-defined func type” which takes an int and returns an int.
This means that any code that uses this type accepts a func with this exact signature:
type Cruncher func(int) int
Declare a few cruncher funcs:
func mul(n int) int {
return n * 2
}func add(n int) int {
return n + 100
}func sub(n int) int {
return n - 1
}

Crunch func processes a series of ints using a variadic Cruncher funcs:
func crunch(nums []int, a ...Cruncher) (rnums []int) { // create an identical slice
rnums = append(rnums, nums...) for _, f := range a {
for i, n := range rnums {
rnums[i] = f(n)
}
} return
}

Declare an int slice with some numbers and process them:
nums := []int{1, 2, 3, 4, 5}crunch(nums, mul, add, sub)
Output:
[101 103 105 107 109]


Anonymous funcs
A noname func is an anonymous func and it’s declared inline using a function literal. It becomes more useful when it’s used as a closure, higher-order func, deferred func, etc.


Signature
A named func:
func Bang(energy int) time.Duration
An anonymous func:
func(energy int) time.Duration
They both have the same signature, so they can be used interchangeably:
func(int) time.Duration


Example
Let’s recreate the cruncher program from the First-Class Funcs section above using the anonymous funcs. Declare the crunchers as anonymous funcs inside the main func.
func main() { crunch(nums,
func(n int) int {
return n * 2
},
func(n int) int {
return n + 100
},
func(n int) int {
return n - 1
})}
This works because, crunch func only expects the Cruncher func type, it doesn’t care that they’re named or anonymous funcs.

To increase the readability you can also assign them to variables before passing to crunch func:
mul := func(n int) int {
return n * 2
}add := func(n int) int {
return n + 100
}sub := func(n int) int {
return n - 1
}crunch(nums, mul, add, sub)


Higher-Order funcs
A higher order func may take one or more funcs or it may return one or more funcs. Basically, it uses other funcs to do its work.

Split func in the closures section below is a higher-order func. It returns a tokenizer func type as a result.

Closures
A closure can remember all the surrounding values where it’s defined. One of the benefits of a closure is that it can operate on the captured environment as long as you want — beware the leaks!

Example
Declare a new func type that returns the next word in a split string:
type tokenizer func() (token string, ok bool)

Split func below is an higher-order func which splits a string by a separator and returns a closure which enables to walk over the words of the split string. The returned closure can use the surrounding variables: “tokens” and “last”.


Let’s try:
const sentence = "The quick brown fox jumps over the lazy dog"iter := split(sentence, " ")for token, ok := iter(); ok; token, ok = iter() {
fmt.Println(token)
}
- Here, I use the split func to split the sentence into words and then get a new iterator func as a result value and put it into the iter variable.
- Then, I start a loop which terminates only when the iter func returns false.
- Each call to the iter func returns the next word.
The result:
The
quick
brown
fox
jumps
over
the
lazy
dog



Concurrent funcs
go func()
runs the passed func concurrently with the other goroutines.
A goroutine is a lighter thread mechanism which allows you to structure concurrent programs efficiently. The main func executes in the main-goroutine.

Example
Here, “start” anonymous func becomes a concurrent func that doesn’t block its parent func’s execution when called with “go” keyword:
start := func() {
time.Sleep(2 * time.Second)
fmt.Println("concurrent func: ends")
}go start()fmt.Println("main: continues...")
time.Sleep(5 * time.Second)
fmt.Println("main: ends")
Output:
main: continues...
concurrent func: ends
main: ends


Other Types
Recursive funcs
You can use recursive funcs as in any other langs, there is no real practical difference in Go. However, you must not forget that each call creates a new call stack. But, in Go, stacks are dynamic, they can shrink and grow depending on the needs of a func. If you can solve the problem at hand without a recursion prefer that instead.

Black hole funcs
A black hole func can be defined multiple times and they can’t be called in the usual ways. They’re sometimes useful to test a parser: see this.
func _() {}
func _() {}

Inlined funcs
Go linker places a func into an executable to be able to call it later at the run-time. Sometimes calling a func is an expensive operation compared to executing the code directly. So, the compiler injects func’s body into the caller. To learn more about them: Read this and this and this (by: Felipe) and this.

External funcs
If you omit the func’s body and only declare its signature, the linker will try to find it in an external func that may have written elsewhere. As an example, Atan func here just declared with a signature and then implemented in here.

Alright, that’s all for now. Thank you for reading so far.
Let’s stay in touch:
- 📩 Join my newsletter
- 🐦 Follow me on twitter
- 📦 Get my Go repository for free tutorials, examples, and exercises
- 📺 Learn Go with my Go Bootcamp Course
- ❤️ Do you want to help? Please clap and share the article. Let other people also learn from this article.