liveBook · Manning

Closure

As explained in Go by Example

A closure is a function that retains access to variables in its surrounding lexical scope after the enclosing function returns.

A closure is a function that retains access to variables in its surrounding scope, including after the outer function returns. It lets a function carry the state or dependencies it needs when that function is called later. section 2.5.2 section 2.5.3

Use closures when behavior needs context that does not fit the eventual function signature. A higher-order function can accept a pointer or dependency, then return a function with a uniform signature that still reaches that captured value. section 4.2.2 section 6.4.1

Bind a flag parser to its destination

A CLI parser can store functions with one parseFunc signature while binding each one to a different configuration field. stringVar receives *string and returns a closure that assigns input; intVar receives *int and returns a closure that converts and assigns input. Calling the stored parser later updates the original field through its captured pointer. section 4.2.2 section 4.2.3

Listing 4.2 Flag value parsers (hit/cmd/hit/config.go)
package main

import "strconv"

type parseFunc func(string) error  #1

func stringVar(p *string) parseFunc {  #2
    return func(s string) error {  #3
        *p = s  #4
        return nil
    }
}

func intVar(p *int) parseFunc {  #5
    return func(s string) error {
        var err error
        *p, err = strconv.Atoi(s)  #6
        return err
    }
}

The returned functions keep their respective pointers while exposing the same parseFunc type, making them suitable for a parser map. section 4.2.2

Closures also adapt function types. A default request sender can capture an HTTP client so it satisfies a simpler SendFunc signature, even though the underlying Send function requires that extra client parameter. This keeps configurable behavior behind one function type. section 6.4.1

Return an HTTP handler with explicit dependencies

A handler factory can accept a logger and URL-shortening service, then return an http.Handler closure. The closure uses those captured dependencies for every request. Passing dependencies directly makes the handler easier to test, since a no-op or inspectable logger can be supplied instead of relying on a global. section 8.4.1

Listing 8.10 Shorten handler (link/rest/shortener.go)
package rest

import (
    "fmt"
    "log/slog"
    "net/http"
    "github.com/inancgumus/gobyexample/link"
)

// Shorten returns an [http.Handler] that shortens URLs.
func Shorten(
    lg *slog.Logger, links *link.Shortener,  #1
) http.Handler {  #2
    return http.HandlerFunc(func(  #3
        w http.ResponseWriter, r *http.Request,   #3
    ) {   #3
        key, err := links.Shorten(r.Context(), link.Link{
            Key: link.Key(  #4
                r.PostFormValue("key"),   #4
            ),   #4
            URL: r.PostFormValue("url"),   #4
        })
        if err != nil {
            httpError(w, r, lg, fmt.Errorf(  #5
                "shortening: %w", err,  #5
            ))   #5
            return  #6
        }

        w.WriteHeader(http.StatusCreated)  #7
        fmt.Fprint(w, key)   #7
    })
}

The handler closure captures the logger and shortener, processes a POST request, and returns immediately after writing an error response. section 8.4.1

Closures can retain pointers beyond the creating function's lifetime. Variables referenced by active closures cannot be reclaimed by the garbage collector, so avoid retaining or improperly sharing long-lived closures when that would keep memory alive unnecessarily. In subtests, each Run closure receives its own *testing.T; do not share a test's *testing.T with other tests. section 2.5.3 section 2.5.2

How Go by Example covers it

How deep it goes
Defines capture and lifetime, then implements pointer-capturing parsers, adapters, handlers, and memory-retention cautions.
Examples
Uses table-driven subtests, CLI flag value parsers, a default HTTP sender, and a URL-shortening handler.
What you can do after
Build it from scratch as parsers, adapters, and handlers that retain pointers or dependencies for later calls.
How it connects
Connects closures to higher-order functions, pointers, garbage collection, subtests, HTTP handlers, and function types.

Also explained in

Compare how 20 books explain closure →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Go by Example. The section links point to the parts of the book it draws on.