Variables of function type
suggest change
Functions are first-class values in Go:
- they can be assigned to variables
- they can be passed as values to functions
Assigning functions to variables
// intOp is a variable whose type is function that takes
// 2 integers as arguments and returns an integer
var intOp func(int, int) int
func intAdd(a, b int) int {
return a + b
}
func main() {
intOp = intAdd
fmt.Printf("intOp(2, 3) = %d\n", intOp(2, 3))
// we can assign literal functions as well
intOp = func(a, b int) int {
return a * b
}
fmt.Printf("intOp(2, 3) = %d\n", intOp(2, 3))
}
intOp(2, 3) = 5
intOp(2, 3) = 6
Passing functions as arguments
func funcAdd(a, b int) int {
return a + b
}
func runFunc(a, b int, intOp func(int, int) int) {
fmt.Printf("intOp(%d, %d) = %d\n", a, b, intOp(a, b))
}
func main() {
runFunc(2, 3, funcAdd)
// we can pass literal functions as well
runFunc(2, 3, func(a, b int) int {
return a * b
})
}
intOp(2, 3) = 5
intOp(2, 3) = 6
Common uses for function arguments:
- filepath.Walk takes a callback function to be called for every file found
- ast.Inspect traverses a tree and calls a function for each node
Comparing functions
A function variable can only be compared to nil
value.
You can't compare a function to another function.
Comparing functions has tricky corner cases so Go designers decided to not implement it at all.
Mocking functionality in tests
Sometimes it's hard to write tests for a piece of code.
Imagine you're writing a web service which needs to authenticate users.
In production deployment this requires looking up user information in the database.
In a test, you don't want to talk to a production database.
One way to enable testing code that calls user authentication is to have two implementations.
One implementation, used in production deployment, talks to the database.
Another implementation is faking the work and is used in tests.
To switch between them we use variable for one level of indirection.
This technique is called mocking.
Here's a sketch of how this might work:
var isUserAdminFn func(string) bool
func isUserAdminProduction(userName string) bool {
// an real implementation that talks to database
return false
}
func isUserAdminMock(userName string) bool {
// a fake implementation used for tests
return userName == "admin"
}
func isUserAdmin(userName string) bool {
return isUserAdminFn(userName)
}
func main() {
isUserAdminFn = isUserAdminProduction
// in test you would use:
// isUserAdminFn = isUserAdminMock
}