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 HandlerFunc
s 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.
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.
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.
©2024 Jonathan Calhoun. All rights reserved.