Interfaces are a very important concept in Go language. They provide a simple and effective way to express common behaviors among types. They give us and easy to understand solution for typical situations where we need some kind of polymorphism. That’s why interfaces are used all the time by Golang developers.

Some of the interfaces are more special than others. Most essential ones are defined in the Go standard library. They are used and can be found in every Go project. Each Golang developer should know these most important interfaces. That way one can easily determine which of the well-known interfaces a given type implements just by looking at methods signatures. It also gives us a grasp of what behaviors we can expect while calling implemented methods of interface that is standard and used everywhere. Standard interfaces also show us how to design good interface (one that will be idiomatic Go code).

In this blog post, I will present some of the most important and good to know interfaces and semantics behind them.

After this, a litte too long introduction let’s see an actual list:

Built-in interface - error [doc]

Error is an built-in interface that describes types that can be treated as error values. Error interface is defined as:

type error interface {
    Error() string
}

As you can see this is an extremely simple interface. Every type in Go that is created to describe some sort of error must implement only one method - Error(). The purpose of it is to provide precise information about the given error including verbose context.

Most of the time you don’t need to create the implementation of this interface by yourself. You can find helper methods in package errors. For example, to create a new error value one can write:

myError := errors.New("Something goes wrong")

If you want to wrap the error around another error and provide more context to it you can use function Errorf from fmt package [doc].

...
if err != nil {
    return fmt.Errorf("Error occured: %v", err)
}
...

If you are looking for a more powerful solution that can help you effectively deal with errors in Go you can use https://github.com/pkg/errors/ package. By using function Wrap from this package you can create meaningful error messages that can also contain function stack traces. This solution is far more superior than using fmt.Errorf.

io.Reader [doc]

This interface is very important for various types of file system and network communication tasks. It is defined like this:

type Reader interface {
    Read(p []byte) (n int, err error)
}

Its definition contains one method Read(). This method will read len(p) bytes from whatever source it is defined for. The bytes will be saved in slice p []byte. This method will return the number of bytes that were read (n) and an error (err) if something went wrong.

For example, if you open a file and then call the Read() method, you will read bytes from that file:

file, err := os.Open("file.txt")
if err != nil {
    log.Fatal(err)
}

defer file.Close()

content := make([]byte, 10)

// Try to read 10 or less bytes in case of EOF
n, err := file.Read(content)

This method also has the same semantics for network connections where you can read data from them, just like from files.

An ioutil package defines method ReadAll which is helpful when you want to read the whole file at once [ doc ] (or read until EOF from whatever source that implements io.Reader interface).

...

file, err := os.Open("file.txt")
...


// ReadAll argument is io.Reader.
// It turns out that struct os.File is implementing this interface,
// as we saw before, so we can use it here.
b, err := ioutil.ReadAll(file),
if err != nil {
    // handle error
}

// b slice contains all bytes of file

By using the io.Reader interface we can wrap one of its implementations around another. This gives us an idiomatic way of achieving things such as:

  • reading from a compressed file
  • reading from a compressed network TCP stream
  • reading from an encrypted network connection

Below is an example of reading from a compressed file:

import "compress/gzip"

...

file, err := os.Open("archive.gz")
...

// Wrap os.File with gzip.Reader
// We can do this beacause gzip.NewReader expects io.Reader implementation
// as argument and os.File is implementing it
decompressReader, err := gzip.NewReader(file)

c := make([]byte, 10)

// Read 10 decompressed bytes
n, err := decompressReader.Read(c)

if err != nil {
    // handle errors
}

a := c[0] // use decompressed data

io.Writer [doc]

This interface is very similar to io.Reader. We use it to write bytes to various destinations. Its definition is also very simple:

type Writer interface {
    Write(p []byte) (n int, err error)
}

This interface has one method - Write(), which takes one argument - the slice of bytes p ([]byte). Then it writes this slice of bytes to some output for which this method is defined. Finally, it returns n - number of bytes that have been written to output and error if there was an error during writing. Simple examples of io.Writer usage my include writing bytes to file or network connection.

This example shows how to write text 'Test\n' to file:

...
file, err := os.Create("file.txt")
if err != nil {
    log.Fatal(err)
}

defer file.Close()

content := []byte("Test\n")

n, err := file.Write(content)
if err != nil {
    log.Printf("Error while writeing to file: %v", err)
}
...

Similar to io.Reader, io.Writer interfaces can be wrapped around each other. This gives us results opposite to io.Reader, for example:

  • writing compressed bytes to file
  • writing compressed bytes to the network connection

This example shows how we can write compressed bytes to file:

import "compress/gzip"

...

file, err := os.Create("file.txt.gz")
if err != nil {
    log.Fatal(err)
}

defer file.Close()

content := []byte("Test\n")

// Wrap os.File with gzip.Writer
compressedWriter := gzip.NewWriter(file)

// Write compressed bytes
n, err := compressedWriter.Write(content)
if err != nil {
    log.Printf("Error while writeing to file: %v", err)
}
...

io.ReadWriter [doc]

This is the first of presented interfaces that is example of interface composition in Golang. This interface is defined like this:

type ReadWriter interface {
    Reader
    Writer
}

As you can see this interface is composed of two other interfaces:

  • io.Reader
  • io.Writer

It represents method set defined for things that you can read from and write to. For example:

  • os.File
  • bytes.Buffer

By defining io.Reader and io.Writer as small one method interfaces we can now compose them into a new one.

io.Closer [doc]

This interface is defined for objects that need to be closed after use. An example that comes to mind immediately is os.File. This interface definition is very simple:

type Closer interface {
    Close() error
}

In this interface, we have only one method - Close. It is used to report a finish of usage of given resource. This method is also important when we write to file using buffered io (package bufio) and we need to make sure that all bytes are saved to file.

Method Close is used in a lot of situations together with defer keyword:

func foo() {
    f, err := os.Open("file.txt")
    if err != nil {
        //error handling
    }

    // Call Close() when we will be returning form current function
    defer func() {
        err := f.Close()
        if err != nil {
            // error when closing file
        }
    }()

    ...

}

io.WriteCloser [doc]

This is next example of interfaces that combines two simple ones into one bigger. This interface is defined like this:

type WriteCloser interface {
    Writer
    Closer
}

It combines the functionality of io.Writer and io.Closer.

io.ReadWriteCloser [doc]

This interface combines three simple interfaces together

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

fmt.Stringer [doc]

This interface functionality is similar to methods like __str__() in Python and toString() in Java. It is used to define text representation of the given object. This interface has one method String():

type Stringer interface {
    String() string
}

This method is invoked implicitly when object is passed to fmt.Printf function and verb is valid for string (%s, %q, %v, %x, %X). Be aware that if an object implements both String() and Error() methods, then the Error() method will be used by fmt.Printf.

fmt.GoStringer [doc]

This interface can be used to change the behavior of Go-syntax representation verb in fmt.Printf format string (%#v). By default, this verb will produce the representation of an object that is valid Go source code. If you want to change this then you need to implement this interface:

type GoStringer interface {
    GoString() string
}

net.Conn [doc]

This interface is more complicated than previous ones. It has more methods and they are designed to work with network data streams.

type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    Close() error
    LocalAddr() Addr
    RemoteAddr() Addr
    SetDeadline(t time.Time) error
    SetReadDeadline(t time.Time) error
    SetWriteDeadline(t time.Time) error
}

The net.Conn is an interface because that way it is easy to test programs that use a network for communication. You can mock this interface by dummy implementation of its methods and test if your network protocol is working well.

You can get ready to use real implementation of net.Conn by using methods from standard library:

  • net.Dial - this method will return connection object which we can use to talk to remote server
  • net.Listener.Accept() - this method will return connection which represents client connected to the server. Method Accept() is defined for interface Listener and how it works depends on the implementation of this interface.

http.ResponseWriter [doc]

This interface is used most often when we are working with HTTP connections. It is used to send data back to the client. It has a simple definition:

type ResponseWriter interface {
    Header() Header
    Write([]byte) (int, error)
    WriteHeader(int)
}

This three methods have very easy to remember semantics:

  • Header() - it gives ability to set custom HTTP headers:
    func handler(w http.ResponseWriter, req *http.Request) {
        w.Header().Set("Content-Type", "text/plain")
    }
  • Write() - sends response body to client:
    func handler(w http.ResponseWriter, req *http.Request) {
        w.Write([]byte("Test"))
    }
  • WriteHeader() - sets HTTP response status code (eg. 200 or 404):
    func handler(w http.ResponseWriter, req *http.Request) {
        w.WriteHeader(http.StatusOK)
    }

Interface ResponseWriter can be mocked using httptest.ResponseRecorder struct [ doc ] which is an implementation of it. That way it is very easy to test HTTP servers in Golang.

image.Image [doc]

This interface represents the read-only image. You can read color data at given coordinate.

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

This interface is very simple and has three methods:

  • ColorModel() - returns information about color space used by image (eg. RGBA)
  • Bounds() - returns image dimensions data
  • At() returns color information at given coordinate

draw.Image [doc]

This interface represents the image that can be modified. It adds the new method to image.Image interface.

type Image interface {
    image.Image
    Set(x, y int, c color.Color)
}

The Set() method can be used to modify color data at given coordinate.

driver.Conn (SQL) [doc]

This interface is used for various SQL server connection implementations.

type Conn interface {
    Prepare(query string) (Stmt, error)
    Close() error
    Begin() (Tx, error)
}

Most of the time you don’t need to use this interface as it is created for SQL driver developers. Normal connection to SQL servers in Golang will involve sql.Open function and sql.BD structure which implements driver.Conn for given SQL server type (eq. Postgresql, MySQL).

sort.Interface [doc]

This interface is used to define the method of comparing data types.

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

It has three methods:

  • Len() - returns size of collection
  • Less() - tells if one of the elements at given indices is smaller than the other
  • Swap() - used to swap elements at given indices in collection

If you want your collection to be sortable by standard Golang functions you must create proper sort.Interface implementation for it.

Conclusion

This post lists some of the most important interfaces in Golang. Of course, this list is not complete because there are a lot more interfaces in Go. The ones in this post are a good starting point and will give you knowledge of what you are dealing with, useful for most of the time.