funciones-anonimas-closures-go

Anonymous Functions and Closures in Go: State and Callbacks

  • 4 min

An anonymous function in Go is a function without a name that we can store, pass around, or execute on the fly. A closure appears when that function remembers variables from the context where it was created.

In the previous article we saw how to declare named functions. In Go, functions are first-class values.

This means that we can handle a function like a value, just as we do with an int or a string.

  • You can store a function in a variable.
  • You can pass a function as an argument to another function.
  • You can return a function from another function.

From this ability arise anonymous functions and closures.

Anonymous Functions

An anonymous function is, literally, a function that has no name. It is defined at the moment it is needed.

The syntax is identical to a normal function, but omitting the identifier after func.

package main

import "fmt"

func main() {
    // 1. We store the function in a variable 'greet'
    greet := func(name string) {
        fmt.Printf("Hello %s, I am anonymous\n", name)
    }

    // 2. We use the variable as if it were a function
    greet("Luis")

    fmt.Printf("The type of 'greet' is: %T\n", greet)
}
Copied!

Immediate Execution (IIFE)

Sometimes we want to declare a function and execute it at the same moment, without storing it in any variable. This is useful for isolating variables in a small scope.

func main() {
    func() {
        fmt.Println("I execute right now and disappear")
    }() // The final parentheses () execute the function
}
Copied!

Functions as Arguments

Since functions are values, we can pass them to other functions. This allows writing flexible and reusable code, such as filters, transformations, or web handlers.

The following function operates on two numbers, while the concrete operation arrives as an argument.

// This function takes two integers AND A FUNCTION that defines the operation
func Operate(a, b int, action func(int, int) int) int {
    return action(a, b)
}

func main() {
    add := func(x, y int) int { return x + y }
    subtract := func(x, y int) int { return x - y }

    fmt.Println(Operate(10, 5, add))      // 15
    fmt.Println(Operate(10, 5, subtract)) // 5
}
Copied!

Closures: Functions that Capture Their Environment

This is where things get interesting.

A Closure is an anonymous function that accesses variables defined outside its own body.

The interesting part is that the anonymous function “captures” or “closes over” those variables. Even if the parent function finishes and its local variables should be destroyed, the closure keeps them alive as long as it exists.

A Sequence Generator

Let’s create a function that returns another function. The returned function is a counter that “remembers” which number it was on.

package main

import "fmt"

// Returns a function that returns an int: func() int
func createSequence() func() int {
    i := 0 // Local variable of createSequence

    // We return an anonymous function (Closure)
    return func() int {
        i++ // This function uses 'i' from outside!
        return i
    }
}

func main() {
    // We create a generator
    next := createSequence()

    fmt.Println(next()) // 1
    fmt.Println(next()) // 2
    fmt.Println(next()) // 3

    // We create ANOTHER independent generator
    another := createSequence()
    fmt.Println(another()) // 1 (It has its own 'i' variable)
}
Copied!

What happened here?

  1. We call createSequence(). The local variable i = 0 is created.
  2. We return the anonymous function, which retains access to that variable.
  3. The compiler guarantees that i remains alive as long as the closure needs it. Its escape analysis will decide whether it must be allocated on the heap; not all closures imply an allocation there.
  4. The variable next contains a function associated with that specific instance of i.

Closures are fundamental for maintaining state without using global variables. Each time you call the creator function (createSequence), a new “environment” with its own variables is created.

Practical Application: Middleware

In Go web development, closures are constantly used to create Middlewares.

We can wrap a function to measure how long it takes without modifying the original function.

func measureTime(action func()) func() {
    return func() {
        start := time.Now()

        action() // Execute the original

        fmt.Println("It took:", time.Since(start))
    }
}
Copied!