Home > Software design >  Adding a interface to a struct [golang]
Adding a interface to a struct [golang]

Time:03-22

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{},
    }
}
  •  Tags:  
  • go
  • Related