Today we continue learning about functions in Go. In this topic, we'll learn about functions that do not have a defined name. These functions are known as anonymous functions or function literals.
Anonymous functions
In Go, you cannot nest named functions. To access variables within the scope of their enclosing function, you need to use anonymous functions.
An anonymous function has the same structure as a regular function, except it doesn't have a name. The function signature of an anonymous function looks like this:
func() {}Now you might be thinking, how to call an anonymous function?
The first way is to assign the anonymous function to a variable, and then call that function using the variable's identifier:
...
func main() {
anonymousDirect := func(message string) { // Direct assign
fmt.Println("Message:", message)
}
anonymousDirect("Direct assign") // Message: Direct assign
var anonymousVariable = func(message string) { // Variable assign
fmt.Println("Message:", message)
}
anonymousVariable("Variable assign") // Message: Variable assign
}Thus, the variable replaces the function name. You can now call that anonymous function by using that variable.
Direct call
And the other way is a direct call to the anonymous function:
...
func main() {
func() {
fmt.Println("Direct call") // Direct call
}()
func(message string) {
fmt.Println("Message:", message) // Message: direct message!
}("direct message!")
}You can't call this function twice because it doesn't have a name. Let's take a closer look at the signature of the anonymous functions call. You might have noticed that both functions have a set of enclosed round brackets () at the end. This set of round brackets tells the runtime to execute the code enclosed within the anonymous function body immediately.
Passing anonymous functions as an argument
Now that you know how to call anonymous functions, you should also know that we can pass them as arguments to other functions. Let's go ahead and take a look at an example:
...
func applyAnonymous(num int, anonymous func(num int)) {
anonymous(num)
}
func main() {
// Call applyAnonymous() with an anonymous function as the second argument:
applyAnonymous(21, func(num int) {
fmt.Println(num * 2) // 42
})
}The above example showcases how an anonymous function gets an int number and outputs the value of the number multiplied by 2. It looks like a first example, using a parameter of a regular function applyAnonymous() you are assigned an anonymous function to a variable name anonymous.
Remember that it is also possible to return values from anonymous functions by specifying the return type. The following example showcases how to create an anonymous function that returns an int type value:
...
func applyAnonymous(num int, anonymous func(int) int) int {
return anonymous(num)
}
func main() {
result := applyAnonymous(21, func(n int) int {
return n * 2
})
fmt.Println(result) // 42
}You must define the complete function signature of the anonymous function, including the return type — anonymous func(int) int.
Shadowing
A common issue you might run into when working with anonymous functions is creating two or more variables with the same name and type. Shadowing is a process of re-declaring variables at the inner scopes. Sometimes it can lead to errors, but the rest of the time causes confusion. In the example below, the variable number is declared in two different scopes:
...
func main() { // Main scope
number := 1
func() { // Inner scope
fmt.Println("Inner scope start:", number)
number := 2
fmt.Println("Inner scope end:", number)
}()
fmt.Println("Main scope:", number)
}
// Output:
// Inner scope start: 1
// Inner scope end: 2
// Main scope: 1You can see, that the number variable has a strange last value output. Let's take apart the inner scope:
the
fmt.Printlnoutputs thenumbervariable value:1from the main scope;a new
numbervariable with value2is declared within the inner scope;the
fmt.Printlnoutputs the newnumbervariable value:2from the inner scope.
Finally, the inner number variable lifecycle is over with the end of the inner scope. The last call of the fmt.Println outputs the original number variable value 1 from the main scope.
In short, the new number variable from the inner scope shadowed the original number variable declared in the main scope.
Closure
A closure is a special type of anonymous function. It can use variables from a parent scope that weren't passed to it as a parameter but instead were available before the anonymous function was declared.
For a better understanding of the concept of closure, let's take a look at the following example:
...
func main() { // parent scope
number := 1
increment := func() {
number += 1
}
fmt.Println("Value before:", number) // Value before: 1
increment()
increment()
fmt.Println("Value after:", number) // Value after: 3
}In the above example, the anonymous function increment() is a closure because it can access the number variable due to the following reasons:
numberforms part of the parent scope —func main();numberwas available beforeincrement()was declared.
Closures with state
The previous example showcased how the increment() anonymous function had access to the number variable because it was defined on its parent scope. However, any other code defined within the main() function also has access to the number variable.
Now you might be wondering, how could we isolate the number variable so that no other code defined in the same scope has access to it?
An important detail about closures is that they can still reference variables that they had access to during creation, even if those variables are no longer referenced elsewhere. To further explain this, let's take a look at the following example:
...
func main() {
increment := newIncrement()
fmt.Println(increment()) // 1
fmt.Println(increment()) // 2
}
func newIncrement() func() int {
var number int
return func() int {
number += 1
return number
}
}Let's explain how the newIncrement() function works. It initializes the number variable and then returns an anonymous function. Here we comply with two conditions: both number and the anonymous function are parts of the same scope — func newIncrement(), and the number variable was available before the declaration of the anonymous function.
Then, within the main() function, we assign increment to the newIncrement() closure. After doing this, increment becomes a function that has access to the number variable that increments by 1 in every call; however, no other code or functions outside the scope of newIncrement() can access the number variable.
You can create as many increment functions as you need. Each call creates a new scope with its own number variable and anonymous function to increment that variable.
Conclusion
An anonymous function is a tool with many uses. You can restrict unwanted access (like in the example above) to a variable, create a setter for a variable (for example, check if the number is included in a defined range), and create a simple function using one line (for example convert degrees to radians), run the inline script (often used with a defer statement) and a lot more. Let's repeat the main points:
An anonymous function is a nameless function —
func(){};Anonymous functions can be declared within another function, be assigned to a variable, and even be declared and called directly;
An anonymous function can access all previously declared variables that are in the same scope.