I have the following
https://go.dev/play/p/s3VizCW1pio
func main() {
type Book struct {
// Bad: pointer changes zero value usefulness
io.ReadWriter
}
// later
var b Book
b.Read([]byte{'1'}) // panic nil value
}
From https://github.com/uber-go/guide
My question is, when we add io.ReadWriter
to Book
, should it compile at all if we haven't implemented the interface io.ReadWriter
for Book
?
There's clearly a gap in my understanding and any help would be greatly appreciated
CodePudding user response:
What you did with type Book
is that you embedded an io.ReadWriter
into it. That means, Book
type has a field of type io.ReadWriter
, and since it is embedded, Book
now has all the methods defined for io.ReadWriter
. You have to set that field to point to an implementation of io.ReadWriter
to use it:
var b Book
myFile,err:=op.Open("someFile") // myFile implements io.ReadWriter
b.ReadWriter=myFile
data:=make([]byte,1)
b.Read(data) // will read from myFile
CodePudding user response:
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{},
}
}