One-function Interfaces in GoLang

Summary

GoLang's http.HandlerFunc pattern provides a concise method for ordinary functions to implement single-method interfaces, reducing the verbosity of traditional struct-based approaches. This pattern works by defining a new type as a function signature, then attaching the required interface method to this function type, which simply calls the underlying function. As a result, any function matching the signature can be adapted to satisfy the interface, exemplified by http.Handle("/path", http.HandlerFunc(myFunc)). While beneficial for simple handlers, maintaining interfaces as types rather than functions is essential for complex scenarios like middleware chaining and managing internal state. This demonstrates Go's flexibility, where even functions can implement interfaces.

Saw a GoLang pattern where a function itself implements an interface—meaning the function alone is the implementation of the interface. It took me a while to figure this out when I first encountered it, so I’m recording it here.

Take server.go from the GoLang standard library as an example. The Handler interface is defined as follows:

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

If we want to define a Handler, we would typically write:

// Define a struct
type MyHandler struct{}

// Implement the http.Handler interface method
func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello from MyHandler!")
}

http.Handle("/hello", MyHandler{})

There are two issues here:

  1. It’s a bit verbose.
  2. The ServeHTTP function, which is closest to the function content, is a required interface method. Its name is fixed but not meaningful, making all handler functions use the same method name.

In modern GoLang, we don’t write handlers this way anymore. Instead, we do:

func myHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello, World!")
}

http.Handle("/hello", http.HandlerFunc(myHandler))

Why does this work? Because the source code contains these lines:

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// [Handler] that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

This short comment carries a deep meaning.

  1. The first line defines type HandlerFunc func(ResponseWriter, *Request), meaning our myHandler function now has the HandlerFunc type.
  2. Every HandlerFunc object has a method called ServeHTTP, which means it satisfies the Handler interface.
  3. The method implementation simply calls the function itself, since the object itself is a function.

In short, any function with the signature ServeHTTP(w ResponseWriter, r *Request) can be converted to a HandlerFunc object. Although it is a function, functions in GoLang are also objects. This means any function with this signature can serve as a Handler.

Thus, we can write:

func myHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello, World!")
}
http.Handle("/hello", http.HandlerFunc(myHandler))

Why not define Handler directly as a function?

type Handler func(ResponseWriter, *Request)

Wouldn’t this achieve the same effect?

The reason is that Handler can be much more complex. For example, GoLang’s middleware mechanism is fundamentally based on chaining Handler calls. More complex Handler implementations may need to maintain internal state, where structs are more suitable than functions. For instance, the httpauth library first initializes a Handler before using it.

What if we still define Handler as a function? Third-party libraries could require initializing an object from their API first and then provide functions that are compatible with Handler. Wouldn’t that work too?

The issue is that different middleware components would have different function signatures, making it impossible to chain them together. However, if Handler is defined as a standard library interface, middleware can accept and return Handler objects, allowing for seamless chaining.

One more interesting point: in GoLang, not only functions but any type can implement an interface. (GoLang truly treats everything as an object!)

Note: The post is authorized to republish and translated from https://www.kawabangga.com/posts/6903

GOLANG ONER FUNCTION INTERFACE

  RELATED

  COMMENTS

0

No comment for this article.