Embedded Types
What you have here is called an embedded type. Embedded types allow you to place other types at the root of a struct and refer to them using their unqualified type name. The methods of the embedded type are available from the parent struct. From a compilation failure perspective, there is no difference between what you have and this:
type Book struct {
writer io.ReadWriter
}
In other words, your example does not require that you specify a value for writer
for the same reason that my example does not -- an embedded value is treated just like any other value other than the embedded type's methods are available from the parent type. In either case, if a value is not provided for the field, then you'll get a runtime nil pointer error.
Should it compile at all if we haven't implemented the interface io.ReadWriter for Book?
It will compile but panic if you try to access the field or its methods. The solution is to provide an implementation by assigning to the embedded field's name.
package main
import (
"bytes"
"io"
)
type Book struct {
io.ReadWriter
}
func main() {
book := Book{
ReadWriter: &bytes.Buffer{},
}
}
Implementing the Interface
If all you want to do is create a Book
struct that implements the io.ReadWriter
interface, then you can get rid of the io.ReadWriter
field on the struct entirely and just do something like this:
package main
import (
"io"
)
var _ io.ReadWriter = &Book{}
type Book struct {}
func (b *Book) Read(p []byte) (n int, err error) {
// Custom implementation here
return 0, err
}
func (b *Book) Write(p []byte) (n int, err error) {
// Custom implementation here
return 0, err
}
func main() {
book := Book{}
}
Custom Wrappers
You can do some pretty cool stuff by embedding types such as wrapping a lower-level implementation of io.ReadWriter
(in this example) with your own custom logic before calling the embedded implementation.
var _ io.ReadWriter = &Book{}
type Book struct {
io.ReadWriter
}
func (b *Book) Read(p []byte) (n int, err error) {
// add a custom behavior before calling the underlying read method
return b.ReadWriter.Read(p)
}
func (b *Book) Write(p []byte) (n int, err error) {
// add a custom behavior before calling the underlying write method
return b.ReadWriter.Write(p)
}
func main() {
book := Book{
ReadWriter: &bytes.Buffer{},
}
}