A while ago, I saw a question in a Go developer Slack channel that went something like this (translated):

When would you want to define a function type like type MyFunc func(msg string) error? Do you have any examples?

I replied with a http.HandlerFunc example (more on that below), but there were many developers smarter than me in that Slack channel, and I ended up learning a lot from the other answers to this question. This article will cover what I took away from the ensuing discussion.

Type definitions

Let’s start off by talking about what the type keyword does. The most common type definitions usually look something like:

type MyStruct struct {
	// Fields...
}

In the case of a struct, or in the case of an interface it might look something like:

type MyDependency interface {
	DoSomething(ctx context.Context) error
}

I daresay more than 90% of the types I define look like the above. However, you may create a type to identify a function signature as well, as defined in the original question. To better understand when we might want to create such a type, let’s have a look at the spec for inspiration. Among other things, the spec has the following to say about type definitions:

A type definition creates a new, distinct type with the same underlying type and operations as the given type, and binds an identifier to it. The new type is called a defined type. It is different from any other type, including the type it is created from. A defined type may have methods associated with it.

The last sentence is key. Let’s see why!

The elegance of http.HandlerFunc

Chances are you’ve played around with the net/http package at least once when learning Go. You may remember that many of the functions in net/http revolve around a type called http.Handler. http.Handler is a single-method interface:

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

It may be tempting to believe that you have to create your own struct that implements the ServeHTTP method, but the net/http provides a very convenient shortcut for us: Whenever you need a http.Handler you can write a http.HandlerFunc instead.

type HandlerFunc func(ResponseWriter, *Request)

This works because the HandlerFunc type implements Handler. In other words, the HandlerFunc has a ServeHTTP(ResponseWriter, *Request) method. The implementation of this method invokes itself:

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

Now that we have an appreciation of how the net/http package works, we can generalise this pattern further:

I can improve my package’s developer experience by defining a Func type that implements a key single-method interface in my package.

You can imagine this pattern being useful where a maintainer of a package regrets requiring an interface (where a function would do), but also doesn’t want to break backwards compatibility.

Function types as lightweight interfaces

I think a lot of us has ingrained the pattern of requesting interfaces when constructing a new type. This pattern scales well and is good practice in general, but as hinted to above there are cases where interfaces are overkill. In this case, requesting a function might be a good solution. By naming your function signature (by defining a function type) you avoid excessively long function signatures just like you would with interfaces. Moreover, your users don’t have to cast their function definitions like you do have to in the case of defining http.HandlerFuncs:

After reflecting on this pattern I decided to change some interfaces into function types in a package I wrote. The diff of this change may be more illustrative than what I can write in words in this blog post. Note that this is a breaking change, but the change required by users of your package are trivial in most cases. To see why, remember that methods are functions too – and as such they can adhere to function type definitions. Where users previously passed in an implementation of an interface, they now have to pass in the function of that implementation. I.e. changing someDep to someDep.FuncImplementation when invoking your package’s function. In fact – your users are now free to rename this function (or even make it private)!

Use function definitions to create default arguments

We can utilize methods on function types when the arguments of a function is almost always the same:

type MyComplicatedFunc func(a string, b int, c *someReallyComplicatedStruct, d onlyThingThatMatters)

func (f MyComplicatedFunc) invoke(d onlyThingThatMatters) {
	f("some default string", 4321, &someReallyComplicatedStruct{ /* values that don't change. */}, d)
}

This should make invoking this function with the ‘default’ arguments internally in the package very simple.

Note: This is a neat trick, but just because you can doesn’t mean you should. I don’t really endorse this pattern, and haven’t really seen it used in production code anywhere.

Use type aliases for documentation

Occasionally, a function parameter could use some additional documentation (including when the parameter itself is a function). You could define a function type in order to attach some additional godocs, however I recommend using type aliases in this case. The only difference is adding a = between the type name and its definition.

type MyComplicatedFunc = func(a string, b int, c *someReallyComplicatedStruct, d onlyThingThatMatters)

The difference between type aliases and type definitions is outside the scope of this article, but one key result is that you cannot attach methods to this type. Type aliases emphasise the intent to only add documentation. Additionally, since this contrived example shows a very long function signature, you can see how type aliases (and type definitions) can make code shorter and more readable.