Why can I pass a struct pointer into a function parameter that is not a struct pointer in Go? [closed]

Take the following struct:

type CustomResponseWriter struct {
    http.ResponseWriter
    StatusCode int
}

func (w *CustomResponseWriter) WriteHeader(statusCode int) {
    w.ResponseWriter.WriteHeader(statusCode)
    w.StatusCode = statusCode
}

Used as:

return http.HandlerFunc(
    func(w http.ResponseWriter, request *http.Request) {
        response := &CustomResponseWriter{
            ResponseWriter: w,
        }

        next.ServeHTTP(response, request)

        logger := NewLogger()
        logger.Info(fmt.Sprintf("%d", response.StatusCode))
    },
)

This works! StatusCode gets properly updated!

But how?

ServerHttp accepts a http.ResponseWriter, but I’m passing a pointer of a struct implementing http.ResponseWriter. Actually, it’s not even a proper http.ResponseWriter because the receiver of WriteHeader is a pointer and not a value as the original interface defines. These types shouldn’t match, but they do, and it all functions correctly. I don’t quite understand.

  • 2

    «ServerHttp accepts a http.ResponseWriter, but I’m passing a pointer of a struct implementing http.ResponseWriter»–…at which time a value of type http.ResponseWriter is created and populated with that pointer (the interface’s dynamic value) and type information (the interface’s dynamic type). When some other code calls WriteHeader, it’s called on the interface value and gets dispatched to the dynamic value stored in that.

    – 

  • 2

    «Actually, it’s not even a proper http.ResponseWriter because the receiver of WriteHeader is a pointer…»–is’t a dubious statement: the receiver of the WriteHeader of a type implementing http.ResponseWriter is a value of that type; if it’s a pointer, it will be a pointer, if it’s not, it will not be a pointer. IOW, if the interface is implemented by T, the receivers of its methods have type T; if it’s implemented by *T, the receivers will have type *T.

    – 

  • 1

    Regarding interfaces, I highly recommend to read this classic piece: research.swtch.com/interfaces; the only thing incorrect in it (these days) is the statement an interface value can contain dynamic values with the sizes up to platform’s pointer size–this is no longer true since Go 1.4 or 1.5.

    – 

  • 1

    You might be also puzzled by why CustomResponseWriter implements http.ResponseWriter. That’s because of “embedding” explained here: «A field declared with a type but no explicit field name is called an embedded field. <…> A field or method f of an embedded field in a struct x is called promoted if x.f is a legal selector that denotes that field or method f.»

    – 

Leave a Comment