Go interfaces & pointers

Interface in Golang is a spec that defines the behavior of a given object.

While working on tradewave data-feeds , I came across the http.Handler interface

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

I was intrigued by the method signature , which accepts Request as a pointer type but ResponseWriter as a value type.

The Request is an actual struct with pointer receiver methods, so passing as a pointer to the method makes sense.

So that begs the question, why isn’t the ResponseWriter passed as a pointer. And in the case of Handler, is ResponseWriter object passed as a value or as a pointer? Logic dictates that incoming response should be a pointer variable so multiple handlers can write to a single response.

Note that everything in Go is passed by value, even pointers. Pointers are special types that contain the memory address of the underlying value, but they are still passed as value.

Further inspection revealed that ResponseWriter is an interface type,

type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
}

An interface at runtime holds an object that implements its interface. Behind the curtains, it holds a 2 row table, a pointer to the type referencing the interface and another pointer to the associated data.

The implementations have no direct binding to the interface. Go can dictate interface implementations and provide compile time type checks.

But it’s still not clear if the writer parameter is a pointer or a value in ServeHTTP method. Lets try out a few examples with interfaces

//(1) The interface
type Mutable interface {
mutate(newValue string) error
}
//(2) Struct
type Data struct {
name string
}
//(3) Implements the interface with a pointer receiver
func (d *Data) mutate(newValue string) error {
d.name = newValue
return nil
}
//(4) Function that accepts the interface
func mutator(mute Mutable) error {
return mute.mutate("mutate")
}
func main() {
d := Data{name: "fresh"}
fmt.Println(d.name) //fresh
//(5) pass as a pointer
mutator(&d)
fmt.Println(d.name) //mutate
}

At step 3, the type Data implements the method as a pointer receiver, which allows the mutation.

And at step 4, the function mutator accepts a parameter of Mutable interface and performs the actual mutating operation. Note the parameter to the function mutator is not a pointer type but just a regular type.

Yet we pass a pointer to the function mutator in Step 5. It seems the function mutator defies the general go-lang type checking. For e.g. this would fail with normal types, but works with the interfaces.

func thisWontWork(s string) { ... }
v := "kirk"
thisWontWork(&v) //compilation error

So it seems that

the parameter type (a value or pointer) passed to a method/function that accepts an interface should match the implementing object’s method receiver
           ---------------------------------------------
| method receiver | parameter |
| ------------------------------------------|
| pointer | pointer |
|-------------------------------------------|
| value | value |
| | pointer (dereferenced) |
---------------------------------------------

Let’s test the above assumptions by changing the receiver to a value type and pass the struct as value.

//(1) Implements the interface with a value receiver
func (d Data) mutate(newValue string) error {
...
//(2) mutator function is UNCHANGED
func mutator(mute Mutable) error {
//(3) pass as value
mutator(d)
fmt.Println(d.name) //fresh

As you can see, the object still satisfies the mutator function requirement. The change is performed on the copy and hence the original value remains unchanged.

Another example that highlights the expected contract,

//(1) Implements the interface with a pointer receiver
func (d *Data) mutate(newValue string) error {
//(2) Function is UNCHANGED
func mutator(mute Mutable) error {
//(3) Pass as value. Results in compilation error.
mutator(d)

Implementations can have different types of receivers and the function would still work,

//(1) Define another struct that implements the interface
type AnotherData struct {...}
//(2) Implements as a value receiver.
func (ad AnotherData) mutate(newValue string) error {...}
//(3) Function is UNCHANGED
func mutator(mute Mutable) error {...}
//(4) Pass as a value.
anotherData := AnotherData{name: "afresh"}
fmt.Println(anotherData.name) //afresh
mutator(anotherData)
fmt.Println(anotherData.name) //afresh

Circling back to the Handler, the response struct implements the ResponseWriter interface with method pointer receivers. The http Server creates the response object and then passes it as a pointer to handlers implementing the Handler interface. This allows handlers to write to a single response object

//server.go - showing relevant code
package http
type response struct {
...
//methods of ResponseWriter implemented by response
func (w *response) Header() Header {...
func (w *response) WriteHeader(code int) {...
//readRequest returns a pointer to the response
func ... readRequest(..) (w *response, err error) {
return &response{...}
}
//serves the request
func (c *conn) serve(ctx context.Context) {
w_ptr, _ = readRequest(...)
...
handler.ServeHTTP(w_ptr)

So it seems that even though interfaces are technically always passed as a value, most often they are passed around as a pointer.

This might be obvious to some of us but it wasn’t obvious to me, especially when looking at just the Handler interface. It did not reveal any information about the expected parameter until I looked up its implementation.

Final thoughts,

  • The interface implementation decides the type of parameter (a pointer or a value) to pass to a method/function that accepts the interface as a parameter.
  • Since both type of receivers are allowed, there could be varying implementations of the same interface.
  • Which could lead to possible runtime errors during type conversion. See airs blog on actual example

Some would argue that method receivers should always be pointers but I would counter-argue that the language should provide a way to enforce it in the interface.

Feedback is welcome.

Further reading:

http://www.airs.com/blog/archives/277: In depth discussion on interfaces

http://research.swtch.com/interfaces: more in depth discussions with pictures

https://github.com/Syerram/crypto_feeder project I am working on