Why Cant I Pass This Function as an Http Handler?!

An incredibly common question I get when helping people learn web development is, “Why can’t I pass this function into the http.Handle function? It is obviously the same as an http.HandlerFunc!”


func demo(h http.Handler) {}

func handler(w http.ResponseWriter, r *http.Request) {}

func main() {
  // This errors at compile time
  demo(handler)
}

I can definitely understand their frustration. We can pass anything explicitly typed as an http.HandlerFunc into that function, and we can pass any function with the format func(http.ResponseWriter, *http.Request) into a function that accepts an http.HandlerFunc, so why doesn’t this code work?

To answer this question we are going to dive into both the http.HandlerFunc and the http.Handler types, as well as looking at various code samples to help illustrate each point.

We will start with the http.HandlerFunc which look like this:

type HandlerFunc func(w ResponseWriter, r *Request)

As a result, we can write code like this:


func demo(fn http.HandlerFunc) {}

func handler(w http.ResponseWriter, r *http.Request) {}

func main() {
  demo(handler)
}

Notice that this code is very different from the original code - our demo function accepts an http.HandlerFunc, NOT an http.Handler.

Another intresting fact your might not know is that in this code handler isn’t actually an http.HandlerFunc. It’s type is actually func(http.ResponseWriter, *http.Request) and we can see this by running the following code:


package main

import (
	"fmt"
	"net/http"
)

func main() {
	fmt.Printf("%T", handler)
}

func handler(w http.ResponseWriter, r *http.Request) {
	// pretend handler func
}

So why are we allowed to pass handler into the demo function in the original example? It clearly isn’t the same type!

Well, in this case our handler function matches the http.HandlerFunc definition exactly so the Go compiler can infer that we wanted to convert our argument into that type. That is, the compiler can basically pretend that our code was written like this:

demo(http.HandlerFunc(demo))

That http.HandlerFunc part is simply derived from the demo function definition.

Okay now we go on to step two - the http.HandlerFunc also implements the http.Handler interface. To see why this is true we just need to look at the source code for both.

type HandlerFunc func(w ResponseWriter, r *Request)

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

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

Notice how HandlerFunc has a ServeHTTP method that matches the one required by the Handler interface, which means that HandlerFunc implements the Handler interface. This may seem odd, but it is completely valid Go code. After all, HandlerFunc is just another type and we add methods to types all the time.

Because HandlerFunc implements the Handler interface, we can also pass HandlerFuncs into functions that accept an Handler as long as the data is explicitly typed as a HandlerFunc. This is shown below.


package main

import "net/http"

func demo(fn http.Handler) {}

func handler(w http.ResponseWriter, r *http.Request) {}

func main() {
	demo(http.HandlerFunc(handler))
}

Alright, so we know http.HandlerFunc implements http.Handler, and we know we can pass objects with the type func(http.ResponseWriter, *http.Request) into a function that requires an http.HandlerFunc, but why can’t we pass func(http.ResponseWriter, *http.Request) into a function that requires an http.Handler? That is, why doesn’t this code compile?

func demo(h http.Handler) {}

func handler(w http.ResponseWriter, r *http.Request) {}

func main() {
  // This errors at compile time
  demo(handler)
}

The short version is that the compiler simply doesn’t know how to convert a func(http.ResponseWriter, *http.Request) into an http.Handler. In order to make that conversion, it would need to know that we wanted to convert handler into an http.HandlerFunc, but as you can see that type isn’t mentioned anywhere in our code.

If we used our inference rule before, the Go compiler could only pretend that the code was instead written this way:

demo(http.Handler(handler))

But that clearly isn’t right. handler doesn’t implement the http.Handler interface unless it is converted into an http.HandlerFunc which DOES implement that interface.

Another way to think about this is that we need to take two steps to get from func(http.ResponseWriter, *http.Request) to http.Handler:

     1              2
func -> HandlerFunc -> Handler

Now if we were to explicitly tell it to convert the handler function into an http.HandlerFunc this code would start to work again.


func demo(h http.Handler) {}

func handler(w http.ResponseWriter, r *http.Request) {}

func main() {
  // This compiles!
  demo(http.HandlerFunc(handler))
}

Again, if we imagined that the Go compiler was pretending the code automatically converted the incoming argument to the type it expects our code would look like this:

demo(http.Handler(http.HandlerFunc(handler)))

Okay, okay, but why doesn’t the compiler just learn how to make those two obvious steps for us? Well, for starters the compiler can’t know for certain that those two steps were the ones you intended to take!

Let’s imagine you had another type, let’s call it a CowboyFunc, and that type was defined like it is below.

type CowboyFunc func(w http.ResponseWriter, r *http.Request)

func (f CowboyFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  s := time.Now()
  f(w, r)
  fmt.Println("Cowboy function duration:", time.Now().Sub(s))
}

Now if we were to call demo(handler) like we were before and it were to automatically convert the handler function into an http.HandlerFunc the code would compile, but is that what the developer really wanted? What if they instead wanted to convert it into a CowboyFunc, which also implements the http.Handler interface?


demo(CowboyFunc(handler))

As you can see this is also valid code, which means the compiler can’t possibly know which of those two we wanted to do without us explicitly telling it.

In summary, it might feel annoying or confusing that you can’t just pass a func(http.ResponseWriter, *http.Request) into any function that expects an http.Handler, but there are solid reasons why this doesn’t work and once you understand them it should give you a better appreciate for those pesky errors you run into every once in a while.

Learn Web Development with Go!

Sign up for my mailing list and I'll send you a FREE sample from my course - Web Development with Go. The sample includes 19 screencasts and the first few chapters from the book.

You will also receive emails from me about Go coding techniques, upcoming courses (including FREE ones), and course discounts.

Avatar of Jon Calhoun
Written by
Jon Calhoun

Jon Calhoun is a full stack web developer who teaches about Go, web development, algorithms, and anything programming. If you haven't already, you should totally check out his Go courses.

Previously, Jon worked at several statups including co-founding EasyPost, a shipping API used by several fortune 500 companies. Prior to that Jon worked at Google, competed at world finals in programming competitions, and has been programming since he was a child.

Related articles

Spread the word

Did you find this page helpful? Let others know about it!

Sharing helps me continue to create both free and premium Go resources.

Want to discuss the article?

See something that is wrong, think this article could be improved, or just want to say thanks? I'd love to hear what you have to say!

You can reach me via email or via twitter.

Recent Articles All Articles Mini-Series Progress Updates Tags About Me Go Courses

©2024 Jonathan Calhoun. All rights reserved.