This is the seventh entry of my weekly series Learning Go. Last week I discussed Function Declarations, Arguments, Parameters, and Anonymous Functions. This week I will be talking about Function Literals and Closure.
Function Literals can be assigned to a variable or called (invoked) directly. They may refer to the variables defined in a surrounding function, making them a closure (we will talk more about this later in the post)
So, what is the difference between a Function Declaration and a Function Literal?
A Function Declaration binds an identifier (the function name) to a function. You can call this function by using its identifier.
A Function Literal is a closure, meaning they can reference variables that have been defined in a surrounding function. These variables can be shared between the function literal and the surrounding function. These variables persist as long as they are accessible.
Let’s start with a basic example and work our way up in complexity.
package main
import (
"fmt"
)
func main() {
f := func() {
fmt.Println("I am a function literal!")
}
f()
// I am a function literal!
}
func main we declare the variable f and assign to an anonymous functionfmt package to print the string I am a function literal!()f is invoked, I am a function literal is printed and the program exitsLet’s see an example when a function literal has a parameter:
package main
import (
"fmt"
)
func main() {
f := func(x int) {
fmt.Println("my birth year is ", x)
}
f(1990)
// my birth year is 1990
}
func main we declare the variable f and assign it to an anonymous function that takes one parameter, x, of type intfmt package, we print the string my birth year is followed by the value of xf is invoked we pass a single argument 1990f prints my birth year is 1990 and the programs exitsNext, let’s see how we can return a function from a Function Literal:
package main
import (
"fmt"
)
func main() {
f := bar()
fmt.Println(f())
// 2020
}
func bar() func() int {
return func() int {
return 2020
}
}
bar:
func main, using the func keyword, we create a function declaration with an identifier of bar with two return types: func() and intbar is expected to return a function and an int inside of that functionbar we return an anonymous function that has a return type of int2020 of type intmain:
func main we declare the variable f and assign it to return value of the function declaration barf is assigned to the return value because we are invoking bar; therefore, what bar returns will be the value that f holds in memory. In this case, that return value is a functionf is invoked on the next line inside of the Println function from the fmt packagebar’s return value is a function that returns the value 2020 of type int: therefore, f() will print 2020As you can see from a few of these examples - function literals can be very powerful and can be used very dynamically in your code. Remember a few things when you are thinking of using a function literal instead of a function declaration:
the way that an anonymous function references variables declared outside of the anonymous function itself
A bit of a brain bender, huh?
The concept of closure can seem very abstract, which makes understanding how they work and the problems they solve difficult as well.
I am confident that seeing closure in action is the best way to learn how they work:
package main
import (
"fmt"
)
func main() {
a := incrementor()
fmt.Println(a())
// 1
fmt.Println(a())
// 2
b := incrementor()
fmt.Println(b())
// 1
}
func incrementor() func() int {
var x int
return func() int {
x++
return x
}
}
incrementor:
incrementor, this should look familiar to bar in the last sectionincrementor is a function declaration that returns a function and an int inside that functionvar keyword we declare the variable x of type intx is not assigned a value; therefore, it is given a zero value (0)return a value of type int++ operator, we are incrementing the value of x by 1 - how is this possible? the answer is closurex, we return xmain:
func main we create the variable a and assign it to the return value of incrementor()a is invoked inside of the Println function from the fmt packagea is the anonymous function inside incrementor(), we increment x by 1 and return the value 1; therefore, 1 is printeda inside of the Println function againa the value of x is 1; therefore, when we increment x the value returned and printed will be 2Notice when we assign incrementor() to the variable b it does not return 3, why is that?
Although a and b were assigned the same return value of incrementor, b has only been invoked once; therefore, it holds it’s own unique value of 1.
This is the power of closure, data isolation. Now, you can easily use common actions across multiple variables, and those variables can have their own, unique values.
I hope you have enjoyed learning about Function Literals and Closure. With the power of closure, you are equipped with another powerful feature of the Go programming language that can make your code more modular, readable, and scalable. Next, I will discuss Recursion and how to apply those principles to your functions. Can’t wait, see you then!