Go Forth and interface{}

Karel Minařík

$ whoami

2

Go and Elastic.co

Application Performance Metrics for #golang

3

~

Source code for this talk:

4

Why are Go interfaces interesting?

5

“Go's interfaces — static, checked at compile time, dynamic when asked for – are, for me, the most exciting part of Go from a language design point of view.

If I could export one feature of Go into other languages, it would be interfaces.”

~ Russ Cox

6

Interfaces allow a certain level of dynamism in a strict language

There are two ways how to look at interfaces:

1. The interface{} type

type Speaker interface{}

2. The empty interface{}

var a interface{}
7

The empty interface{}

$ go doc fmt Println
func Println(a ...interface{}) (n int, err error)
# -> Fprintln() -> doPrintln() -> printArg()

$ godoc --src fmt printArg
# ...
switch f := arg.(type) {
case bool:
    p.fmtBool(f, verb)
# ...
case string:
    p.fmtString(f, verb)
}

$ go doc fmt.Stringer
type Stringer interface {
  String() string
}
8

The interface{} type

9

The interface{} type

type Speaker interface {
    Say(string)
}

type Person struct {
    Name string
}

func (p *Person) Say(msg string) {
    fmt.Println(p.Name + " says: " + msg)
}
package main

import (
	"fmt"
)

// START1 OMIT
type Speaker interface {
	Say(string) // HL
}

type Person struct {
	Name string
}

func (p *Person) Say(msg string) { // HL
	fmt.Println(p.Name + " says: " + msg)
}

// END1 OMIT

func main() {
    p := Person{"John"}
    p.Say("Hello Gophers!")
}
10

The interface{} type

type Speaker interface {
    Say(string)
}

type Person struct {
    Name string
}

func (p *Person) Say(msg string) {
    fmt.Println(p.Name + " says: " + msg)
}

n.b.

Implicit satisfaction (no implements clause)

11

The interface{} type

// # Person implementation omitted

type Robot struct {
    Name string
}

func (r *Robot) Say(msg string) {
    fmt.Println(r.Name + " says: " + hex.EncodeToString([]byte(msg)))
}
package main

import (
	"encoding/hex"
	"fmt"
)

type Speaker interface {
	Say(string)
}

type Person struct {
	Name string
}

func (p *Person) Say(msg string) {
	fmt.Println(p.Name + " says: " + msg)
}

// START1 OMIT
// # Person implementation omitted

type Robot struct {
	Name string
}

func (r *Robot) Say(msg string) {
	fmt.Println(r.Name + " says: " + hex.EncodeToString([]byte(msg))) // HL
}

// END1 OMIT

func main() {
    p := Person{"John"}
    p.Say("Hello Gophers!")

    r := Robot{"R2D2"}
    r.Say("Hello Gophers!")
}
12

The interface{} type

// # Person and Robot implementation omitted

func SayLoud(speaker Speaker, msg string) {
    fmt.Println(strings.Repeat("!", 80))
    speaker.Say(msg)
    fmt.Println(strings.Repeat("!", 80))
    fmt.Println()
    // os.Exec "say ..."
}
package main

import (
	"encoding/hex"
	"fmt"
	"strings"
)

type Speaker interface {
	Say(string)
}

type Person struct {
	Name string
}

func (p *Person) Say(msg string) {
	fmt.Println(p.Name + " says: " + msg)
}

type Robot struct {
	Name string
}

func (r *Robot) Say(msg string) {
	fmt.Println(r.Name + " says: " + hex.EncodeToString([]byte(msg)))
}

// START1 OMIT
// # Person and Robot implementation omitted

func SayLoud(speaker Speaker, msg string) { // HL
	fmt.Println(strings.Repeat("!", 80))
	speaker.Say(msg)
	fmt.Println(strings.Repeat("!", 80))
	fmt.Println()
	// os.Exec "say ..."
}

// END1 OMIT

func main() {
    p := Person{"John"}
    SayLoud(&p, "Hello Gophers!")

    r := Robot{"R2D2"}
    SayLoud(&r, "Hello Gophers!")
}

n.b.

⇢ The SayLoud() function accepts an interface

13

The interface{} type

type Speaker interface {
    Say(string)
    SayTo(io.Writer, string)
}
func (p *Person) Say(msg string) {
    fmt.Println(p.statement(msg))
}

func (p *Person) SayTo(w io.Writer, msg string) {
    fmt.Fprint(w, p.statement(msg))
}

func (p *Person) statement(msg string) string { return p.Name + " says: " + msg + "\n" }
package main

import (
	"fmt"
	"io"
	"os"
)

// START1 OMIT
type Speaker interface {
	Say(string)
	SayTo(io.Writer, string) // HL
}

// END1 OMIT

type Person struct {
	Name string
}

// START2 OMIT
func (p *Person) Say(msg string) {
	fmt.Println(p.statement(msg))
}

func (p *Person) SayTo(w io.Writer, msg string) { // HL
	fmt.Fprint(w, p.statement(msg))
}

func (p *Person) statement(msg string) string { return p.Name + " says: " + msg + "\n" }

// END2 OMIT

// type Robot struct {
// 	Name string
// }

// func (r *Robot) Say(msg string) {
// 	fmt.Println(r.statement(msg))
// }

// func (r *Robot) SayTo(w io.Writer, msg string) {
// 	fmt.Fprint(w, r.statement(msg)+"\n")
// }

// func (r *Robot) statement(msg string) string {
// 	return r.Name + " says: " + hex.EncodeToString([]byte(msg))
// }

func main() {
    p := Person{"John"}
    p.Say("Hello!")
    p.SayTo(os.Stdout, "Gophers!")
}
14

The interface{} type

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
)

// START1 OMIT
type Speaker interface {
	Say(string)
	SayTo(io.Writer, string) // HL
}

// END1 OMIT

type Person struct {
	Name string
}

// START2 OMIT
func (p *Person) Say(msg string) {
	fmt.Println(p.statement(msg))
}

func (p *Person) SayTo(w io.Writer, msg string) { // HL
	fmt.Fprint(w, p.statement(msg))
}

func (p *Person) statement(msg string) string { return p.Name + " says: " + msg + "\n" }

// END2 OMIT

func main() {
    p := Person{"John"}

    // 1. STDOUT
    fmt.Println(">")
    p.SayTo(os.Stdout, "Gophers!")

    // 2. FILE
    f, _ := os.OpenFile("/tmp/say.txt", os.O_RDWR|os.O_CREATE, 0755)
    defer f.Close()
    p.SayTo(f, "File!")

    fmt.Printf("\n$ cat %s\n", f.Name())
    out, _ := exec.Command("cat", "/tmp/say.txt").Output()
    fmt.Printf("%s\n", out)

    // 3. HTTP
    addr := "localhost:4000"
    fmt.Println("http://" + addr)
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        p.SayTo(w, "HTTP!")
    })

    http.ListenAndServe(addr, nil)
}
15

The interface{} type

package main

import (
	"bytes"
	"fmt"
	"io"
	"os/exec"
	"strings"
)

// START1 OMIT
type Speaker interface {
	Say(string)
	SayTo(io.Writer, string) // HL
}

// END1 OMIT

type Person struct {
	Name string
}

// START2 OMIT
func (p *Person) Say(msg string) {
	fmt.Println(p.statement(msg))
}

func (p *Person) SayTo(w io.Writer, msg string) { // HL
	fmt.Fprint(w, p.statement(msg))
}

func (p *Person) statement(msg string) string { return p.Name + " says: " + msg + "\n" }

// END2 OMIT

func main() {
    p := Person{"Victoria"}

    var out bytes.Buffer
    p.SayTo(&out, "Hello, Gophers!")

    cmd := []string{"say", "--voice", p.Name, out.String()}

    fmt.Printf("$ %s", strings.Join(cmd, " "))
    exec.Command(cmd[0], cmd[1:]...).Output()
}

Say it loud :)

16

The interface{} type

type Speaker interface {
    Say(string)
    SayTo(io.Writer, string)
    Statement() io.Reader
}
func (p *Person) Say(msg string) { io.Copy(os.Stdout, p.Statement(msg)) }

func (p *Person) SayTo(w io.Writer, msg string) { io.Copy(w, p.Statement(msg)) }

func (p *Person) Statement(msg string) io.Reader {
    return strings.NewReader(p.Name + " says: " + msg + "\n")
}
package main

import (
	"io"
	"os"
	"strings"
)

// START1 OMIT
type Speaker interface {
	Say(string)
	SayTo(io.Writer, string)
	Statement() io.Reader // HL
}

// END1 OMIT

type Person struct {
	Name string
}

// START2 OMIT
func (p *Person) Say(msg string) { io.Copy(os.Stdout, p.Statement(msg)) }

func (p *Person) SayTo(w io.Writer, msg string) { io.Copy(w, p.Statement(msg)) }

func (p *Person) Statement(msg string) io.Reader { // HL
	return strings.NewReader(p.Name + " says: " + msg + "\n")
}

// END2 OMIT

func main() {
    p := Person{"John"}

    p.Say("Hello, streams!")
}
17

io.Reader & io.Writer

18

Interface as an abstraction

$ go doc io.Reader

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

$ go doc io.Writer

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

$ go doc io.Copy

func Copy(dst Writer, src Reader) (written int64, err error)
19

“The io.Reader interface is very simple; Read reads data into the supplied buffer, and returns to the caller the number of bytes that were read, and any error encountered during read. It seems simple but it’s very powerful.”

“Because io.Reader‘s deal with anything that can be expressed as a stream of bytes, we can construct readers over just about anything; a constant string, a byte array, standard in, a network stream, a gzip’d tar file, the standard out of a command being executed remotely via ssh.”

~ Dave Cheney

20

“If you’re designing a package or utility (even if it’s an internal thing that nobody will ever see) rather than taking in strings or []byte slices, consider taking in an io.Reader if you can for data sources. Because suddenly, your code will work with every type that implements io.Reader.”

Mat Ryer

21

Save a document

// Save writes the contents of doc to the file f.
func Save(f *os.File, doc *Document) error

vs

// Save writes the contents of doc to the supplied Writer.
func Save(w io.Writer, doc *Document) error

“Because Save operates directly with files on disk, it is unpleasant to test. (…) *os.File also defines a lot of methods which are not relevant to Save (…)”

~ Dave Cheney

22

Save a document

type Document struct {
    Name    string
    Content io.Reader
}

func Save(w io.Writer, doc *Document) error {
    _, err := io.Copy(w, doc.Content)
    return err
}
package main

import (
	"bytes"
	"fmt"
	"io"
	"strings"
)

// START1 OMIT
type Document struct {
	Name    string
	Content io.Reader
}

func Save(w io.Writer, doc *Document) error {
	_, err := io.Copy(w, doc.Content) // HL
	return err
}

// END1 OMIT

func main() {
    var b bytes.Buffer

    doc := &Document{Name: "Test", Content: strings.NewReader("HELLO")}

    Save(&b, doc)

    fmt.Printf("%T: %q\n", b, b.String())
}
23

Save a document to a file

package main

import (
	"fmt"
	"io"
	"os"
	"strings"
)

// START1 OMIT
type Document struct {
	Name    string
	Content io.Reader
}

func Save(w io.Writer, doc *Document) error {
	_, err := io.Copy(w, doc.Content) // HL
	return err
}

// END1 OMIT

func main() {
    f, _ := os.Create("/tmp/document.txt")
    defer f.Close()

    fi, _ := f.Stat()
    fmt.Printf("File: %s [%dB]\n", fi.Name(), fi.Size())

    doc := &Document{Name: "Test", Content: strings.NewReader("HELLO")}

    Save(f, doc)

    fi, _ = f.Stat()

    fmt.Printf("File: %s [%dB]\n", fi.Name(), fi.Size())
}
24

Save a document to multiple destinations

package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
)

type Document struct {
	Name    string
	Content io.Reader
}

func Save(w io.Writer, doc *Document) error {
	_, err := io.Copy(w, doc.Content)
	return err
}

func main() {
    var b bytes.Buffer

    var f, _ = os.Create("/tmp/document.txt")
    defer f.Close()

    doc := &Document{Name: "Test", Content: strings.NewReader("HELLO")}

    var mw = io.MultiWriter(&b, f)

    Save(mw, doc)

    fi, _ := f.Stat()

    fmt.Printf("%T: %q\n", b, b.String())
    fmt.Printf("File: %s [%dB]\n", fi.Name(), fi.Size())
}
25

Save a document to /dev/null

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"strings"
)

type Document struct {
	Name    string
	Content io.Reader
}

func Save(w io.Writer, doc *Document) error {
	_, err := io.Copy(w, doc.Content)
	return err
}

func main() {
    doc := &Document{Name: "Test", Content: strings.NewReader("HELLO")}
    err := Save(ioutil.Discard, doc)

    fmt.Printf("Error: %v\n", err)
}
26

Save a document and verify SHA256 digest (I)

func Save(w io.Writer, doc *Document) error {
    bw := bytes.NewBuffer([]byte{})
    tr := io.TeeReader(doc.Content, bw)
    dw := sha256.New()

    io.Copy(dw, tr)

    digest := fmt.Sprintf("%x", dw.Sum(nil))

    if digest != doc.Digest {
        return fmt.Errorf("failed to save document: digest mismatch, %q != %q", digest, doc.Digest)
    }

    _, err := io.Copy(w, bw)
    return err
}
27

Save a document and verify SHA256 digest (II)

package main

import (
	"bytes"
	"crypto/sha256"
	"fmt"
	"io"
	"os"
	"strings"
)

type Document struct {
	Name    string
	Digest  string
	Content io.Reader
}

// START1 OMIT
func Save(w io.Writer, doc *Document) error {
	bw := bytes.NewBuffer([]byte{})     // HL
	tr := io.TeeReader(doc.Content, bw) // HL
	dw := sha256.New()                  // HL

	io.Copy(dw, tr) // HL

	digest := fmt.Sprintf("%x", dw.Sum(nil))

	if digest != doc.Digest {
		return fmt.Errorf("failed to save document: digest mismatch, %q != %q", digest, doc.Digest)
	}

	_, err := io.Copy(w, bw)
	return err
}

// END1 OMIT

func main() {
    doc1 := &Document{Name: "Test", Content: strings.NewReader("HELLO\n"), Digest: "foobar"}
    err := Save(os.Stdout, doc1)

    fmt.Fprintf(os.Stderr, "Error: %v\n", err)

    doc2 := &Document{Name: "Test", Content: strings.NewReader("HELLO\n"), Digest: "3b09aeb6f5f5336beb205d7f720371bc927cd46c21922e334d47ba264acb5ba4"}
    err = Save(os.Stdout, doc2)

    fmt.Printf("Error: %v\n", err)
}
28

Save a document and verify the content size (I)

func Save(w io.Writer, doc *Document) error {
    maxBytes := int64(1 * 1024 * 1042)                                                // 1MB
    br := http.MaxBytesReader(RWriter{w: w}, ioutil.NopCloser(doc.Content), maxBytes)
    bw := bytes.NewBuffer([]byte{})

    _, err := io.Copy(bw, br)
    if err != nil {
        return fmt.Errorf("document content too big: > %dMB", maxBytes/1024/1024)
    }

    _, err = io.Copy(w, bw)
    return err
}
29

Save a document and verify the content size (II)

package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
)

type Document struct {
	Name    string
	Digest  string
	Content io.Reader
}

// Implement http.ResponseWriter

type RWriter struct {
	w io.Writer
}

func (w RWriter) Header() http.Header { return http.Header{} }

func (w RWriter) WriteHeader(s int) {}

func (w RWriter) Write(b []byte) (int, error) {
	return w.w.Write(b)
}

// START1 OMIT
func Save(w io.Writer, doc *Document) error {
	maxBytes := int64(1 * 1024 * 1042)                                                // 1MB
	br := http.MaxBytesReader(RWriter{w: w}, ioutil.NopCloser(doc.Content), maxBytes) // HL
	bw := bytes.NewBuffer([]byte{})                                                   // HL

	_, err := io.Copy(bw, br) // HL
	if err != nil {
		return fmt.Errorf("document content too big: > %dMB", maxBytes/1024/1024)
	}

	_, err = io.Copy(w, bw)
	return err
}

// END1 OMIT

func main() {
    c, _ := ioutil.ReadFile("/usr/share/dict/words")
    fmt.Printf("Content size: ~ %dMB\n", len(c)/1024/1024)

    doc := &Document{Name: "Test", Content: bytes.NewBuffer(c)}
    err := Save(ioutil.Discard, doc)

    fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
30

Mock a writer to always fail

type ErrorWriter struct{}

func (w ErrorWriter) Write(b []byte) (int, error) {
    return 0, fmt.Errorf("MOCK ERROR")
}
package main

import (
	"fmt"
	"io"
	"strings"
)

type Document struct {
	Name    string
	Content io.Reader
}

func Save(w io.Writer, doc *Document) error {
	_, err := io.Copy(w, doc.Content) // HL
	return err
}

// START1 OMIT

type ErrorWriter struct{} // HL

func (w ErrorWriter) Write(b []byte) (int, error) {
	return 0, fmt.Errorf("MOCK ERROR") // HL
}

// END1 OMIT

func main() {
    doc := &Document{Name: "Test", Content: strings.NewReader("HELLO")}
    err := Save(ErrorWriter{}, doc)

    if err != nil {
        fmt.Printf("FAIL: %v\n", err)
    } else {
        fmt.Println("OK")
    }
}
31

Conclusions

32

Thank You

 Questions, please!

Karel Minařík