Skip to content

Instantly share code, notes, and snippets.

@Integralist
Last active September 1, 2020 09:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Integralist/b2e87acad7fdf354ade3250dcb31c168 to your computer and use it in GitHub Desktop.
Save Integralist/b2e87acad7fdf354ade3250dcb31c168 to your computer and use it in GitHub Desktop.
[Go embedding interface types] #go #golang #interface #struct #field #type #assertion #embed #override #template #pattern

Embedding an interface into a struct means that field will need to be assigned a value that fulfils the interface.

This forces the struct to implement the given methods because when you assign a type to that field it needs to have those methods available.

This is especially useful when you want to re-implement only a single method of the interface for testing purposes as noted here...

https://dmv.myhatchpad.com/insight/mocking-techniques-for-go/

Use when you need to mock out a small set of methods defined in a large interface.

A great example of this situation comes from the DynamoDB documentation.

When working with the aws-sdk, they provide interfaces for all of their major services that are quite large since they contain all of the calls that can be made for each particular client. Take a look at the dynamodbiface.DynamoDBAPI interface from the link. Rather than pass around the concrete client type, you should pass around this interface to other functions. But then, when testing some of your code that calls one particular function of the interface, how do you mock out that call only without mocking every other function in an attempt to satisfy the interface?

WARNING: assigning nil breaks this compile time safety!

This is best demonstrated by way of an example (https://play.golang.org/p/i8q0gv4WgkQ)

package main

import (
	"fmt"
	"io"
)

// Foo needs to implement io.Reader
type Foo struct {
	io.Reader
	Bar int
}

// Bar doesn't implement io.Reader
type Bar struct{}

// Baz DOES implement io.Reader
type Baz struct{}

func (b Baz) Read(p []byte) (n int, err error) {
	fmt.Println("Bar.Read was called")
	return 0, nil
}

func main() {
	// we can assign Foo{Reader: Baz{}} to r
	// as it fulfils the io.Reader interface
	//
	var r io.Reader
	r = Foo{Reader: Baz{}}
	fmt.Printf("%+v\n", r) // {Reader:{} Bar:0}

	// WARNING: this compiles but then fails at runtime ❌
	// i.e. a nil value breaks compile time safety!
	//
	r = Foo{}
	fmt.Printf("%+v\n", r) // {Reader:<nil> Bar:0}

	b := []byte{}
	r.Read(b)
	//
	// panic: runtime error: invalid memory address or nil pointer dereference

    // What's much better (of course) is getting a COMPILE TIME error!
	//
    // if we tried to assign `Foo{Reader: Bar{}}` to r variable then we'd get compiler error.
	// because we cannot use Bar literal (type Bar) as type io.Reader in field value
	// as Bar does not implement io.Reader (it's missing the Read method)
	//
	fmt.Printf("%+v\n", Foo{Reader: Bar{}})

}

When not embedding an interface we can use an underscore as a 'compile time check' trick...

package main

import (
	"net/http"
)

type RW struct {}

/*
func (rw RW) Header() http.Header {
	return make(http.Header)
}
*/

func (rw RW) Write([]byte) (int, error) {
	return 0, nil
}

func (rw RW) WriteHeader(statusCode int) {}

// ./prog.go:21:5: cannot use RW literal (type RW) as type http.ResponseWriter in assignment:
//  	RW does not implement http.ResponseWriter (missing Header method)
var _ http.ResponseWriter = RW{}

func main() {
	// ...
}

But as mentioned earlier be aware that the above example code would fail compilation because we didn't embed the interface! Once you embed the interface the compile time error doesn't occur!

This is because when embedding the interface you're providing a nil value for the ResponseWriter field and that's fine, as far as the compiler is concerned when it comes to saying have you provided something to satisfy this field's requirements. But of course nil has no methods!

So be careful because you can run into issues like the following code which compiles fine but then fails at runtime with a panic!

package main

import (
	"fmt"
	"net/http"
)

var _ http.ResponseWriter = RW{}

type RW struct {
	http.ResponseWriter
}

/*
func (rw RW) Header() http.Header {
	return make(http.Header)
}
*/

func (rw RW) Write([]byte) (int, error) {
	return 0, nil
}

func (rw RW) WriteHeader(statusCode int) {
	fmt.Println(statusCode)
}

func main() {
	var rw http.ResponseWriter
	rw = RW{}
	fmt.Printf("%+v\n", rw) // {ResponseWriter:<nil>}

	written, err := rw.Write([]byte("ABC"))
	fmt.Printf("%+v %+v\n", written, err)

	rw.WriteHeader(123)

	fmt.Printf("%+v\n", rw.Header())
}
// Package middleware provides wrapper functions around the http.Handler
// interface, allowing for an incoming HTTP request to be modified or analysed.
package middleware
import (
"fmt"
"net/http"
"time"
)
// This is a compile-time check to ensure that `Handler` meets
// the interface definition for `http.ResponseWriter`.
//
// We throw away the result because anything other than an error means the check
// passes, and an error will prevent compiling.
//
// Not defining the underlying `ResponseWriter` field in the Handler struct
// will effectively be the same as passing `http.ResponseWriter(nil)`. Which is
// enough to associate the relevant methods with the Handler struct so it may
// satisfy the http.ResponseWriter interface.
var (
_ http.ResponseWriter = &Handler{}
)
// Handler provides an interface composed of an `http.ResponseWriter`
// to satisfy the http handler interface to allow us to expose a request
// to our logger.
type Handler struct {
http.ResponseWriter
status int
length int
}
// WriteHeader writes status field as the response status via the underlying
// ResponseWriter instance created at the time of the request construction.
func (h *Handler) WriteHeader(status int) {
h.status = status
h.ResponseWriter.WriteHeader(status)
}
// Write writes `statusOK` for any handler passed in that has the status
// (represented by an int) of 0. Any other status code will be left as is,
// and then the status will be handed off to the ResponseWriter.
func (h *Handler) Write(b []byte) (int, error) {
if h.status == 0 {
h.status = http.StatusOK
}
n, err := h.ResponseWriter.Write(b)
h.length += n
return n, err
}
// Foo is using the traditional golang middleware pattern for handling web server requests.
func Foo(handler http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h := &Handler{ResponseWriter: w}
handler.ServeHTTP(h, r)
fmt.Println(h.status) // should now have a value as it's passed through the various middleware
}
}
type baz interface {
...
}
type X struct {
foo int
bar baz
}
convertThingThatImplementsBazIntoInterfaceType := interface{}(ThingThatImplementsBaz{})
realTypeReturned := convertThingThatImplementsBazIntoInterfaceType.(baz)
x.bar = realTypeReturned
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment