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