Go Forth and interface{}
Karel Minařík
Karel Minařík
    Application Performance Metrics for #golang
  
github.com/elastic/apm-agent-go
3Source code for this talk:
github.com/karmi/gotalks/tree/master/2018/interface/code
4“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
6There are two ways how to look at interfaces:
    1. The interface{} type
  
type Speaker interface{}
    2. The empty interface{}
  
var a interface{}fmt.Println()$ 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
}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!")
}
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)
  
// # 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!")
}
// # 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
  
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!")
}
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)
}
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 :)
16type 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!")
}
$ 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)“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.”
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 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 (…)”
  
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())
}
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())
}
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())
}
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)
}
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 }
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)
}
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 }
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)
}
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") }
}
Stringer)Karel Minařík