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())
}