What Makes Golang Go
The Power of Go Interfaces
4 May 2021
Ricardo Gerardi
Powerful Command-Line Applications in Go, Author
Senior Automation Consultant, Red Hat
Ricardo Gerardi
Powerful Command-Line Applications in Go, Author
Senior Automation Consultant, Red Hat
There are no:
A way to enable seemingly incompatible entities to interact
The Abstract Type
var power int type Jedi struct { FirstName string LastName string Age int Rank string } func foo() { name := "Luke" // string }
type Jedi struct { FirstName string LastName string Age int Rank string } func (j Jedi) ForcePush(p int) int { // TODO Method implementation return p } type power int func (p power) unleash() { // TODO Method implementation }
type ForceUser interface { ForcePush(int) int }
package main import "fmt" type Jedi struct { FirstName string LastName string Age int Rank string } func main() { luke := Jedi{ FirstName: "Luke", LastName: "Skywalker", Age: 22, Rank: "Master", } fmt.Println(luke) }
{Luke Skywalker 22 Master} Program exited.
package main
import "fmt"
type Jedi struct { FirstName string LastName string Age int Rank string } func (j Jedi) String() string { return j.FirstName + " " + j.LastName } func main() { luke := Jedi{ FirstName: "Luke", LastName: "Skywalker", Age: 22, Rank: "Master", } fmt.Println(luke) }
// Stringer is implemented by any value that has a String method, // which defines the ``native'' format for that value. // The String method is used to print values passed as an operand // to any format that accepts a string or to an unformatted printer // such as Print. type Stringer interface { String() string }
package force type Jedi struct { FirstName string LastName string Age int Rank string } func (j Jedi) String() string { return j.FirstName + " " + j.LastName }
package main import ( "fmt" "github.com/rgerardi/philly-ete-2021/print3/force" ) func main() { luke := force.Jedi{ FirstName: "Luke", LastName: "Skywalker", Age: 22, Rank: "Master", } fmt.Println(luke) }
package main import ( "fmt" "io/ioutil" "os" ) func main() { fmt.Println("Writing data") data := []byte("Some data to write") f, _ := ioutil.TempFile("/tmp", "data") defer f.Close() save(f, data) } func save(f *os.File, data []byte) error { _, err := f.Write(data) return err }
// Writer is the interface that wraps the basic Write method. // // Write writes len(p) bytes from p to the underlying data stream. // It returns the number of bytes written from p (0 <= n <= len(p)) // and any error encountered that caused the write to stop early. // Write must return a non-nil error if it returns n < len(p). // Write must not modify the slice data, even temporarily. // // Implementations must not retain p. type Writer interface { Write(p []byte) (n int, err error) }
package main import ( "fmt" "io" "io/ioutil" ) func main() { fmt.Println("Writing data") data := []byte("Some data to write") f, _ := ioutil.TempFile("/tmp", "data") defer f.Close() save(f, data) } func save(w io.Writer, data []byte) error { _, err := w.Write(data) return err }
package main
import (
"fmt"
"io/ioutil"
"net"
"os"
)
func main() { ln, err := net.Listen("tcp", ":3000") if err != nil { os.Exit(1) } fmt.Println("Listening on port 3000") for { conn, err := ln.Accept() if err != nil { os.Exit(1) } go handleConnection(conn) } } func handleConnection(conn net.Conn) { msg, err := ioutil.ReadAll(conn) if err != nil { os.Exit(1) } fmt.Printf("%s\n", msg) conn.Close() }
package main import ( "fmt" "io" "net" ) func main() { fmt.Println("Writing data") data := []byte("Some data to write") client, _ := net.Dial("tcp", ":3000") defer client.Close() save(client, data) } func save(w io.Writer, data []byte) error { _, err := w.Write(data) return err }
package main
import (
"fmt"
"io"
"net"
)
func main() { fmt.Println("Writing data") data := []byte("Some data to write") client, _ := net.Dial("tcp", ":3000") defer client.Close() save(LogWriter(client), data) } type logWriter struct { writer io.Writer } func (l logWriter) Write(p []byte) (n int, err error) { fmt.Printf("%s\n", p) return l.writer.Write(p) } func LogWriter(w io.Writer) io.Writer { return logWriter{w} }
func save(w io.Writer, data []byte) error {
_, err := w.Write(data)
return err
}
func save(f *os.File, data []byte) error { _, err := f.Write(data) return err }
func Test_Save(t *testing.T) { testdata := "Test data" f, err := ioutil.TempFile("/tmp", "data") if err != nil { t.Fatal("Error creating test file") } defer os.Remove(f.Name()) if err := save(f, []byte(testdata)); err != nil { t.Fatal("Error saving data") } f.Close() data, _ := ioutil.ReadFile(f.Name()) if string(data) != testdata { t.Errorf("Expected %s, got %s\n", testdata, string(data)) } }
func save(w io.Writer, data []byte) error { _, err := w.Write(data) return err }
func Test_Save(t *testing.T) { buf := &bytes.Buffer{} testdata := "Test data" if err := save(buf, []byte(testdata)); err != nil { t.Fatal("Error saving data") } if buf.String() != testdata { t.Errorf("Expected %s, got %s\n", testdata, buf.String()) } }
=== RUN Test_Save
--- PASS: Test_Save (0.00s)
PASS
ok github.com/rgerardi/philly-ete-2021/testing/ex2 0.006s
23
"If it walks like a duck and it quacks like a duck, then it must be a duck"
en.wikipedia.org/wiki/Duck_typing
In Go:
"If it walks like a duck and it quacks like a duck, then it BEHAVES like a duck"
25package shape import "math" type Circle struct { Radius float64 } func (c Circle) Perimeter() float64 { return math.Pi * 2 * c.Radius } type Rectangle struct { Length float64 Width float64 } func (r Rectangle) Perimeter() float64 { return 2 * (r.Length + r.Width) }
package main import ( "fmt" "github.com/rgerardi/philly-ete-2021/shape/shape" ) type border interface { Perimeter() float64 } func main() { c := shape.Circle{5} r := shape.Rectangle{20, 10} printBorder(c) printBorder(r) } func printBorder(b border) { fmt.Println("Border length is", b.Perimeter()) }
package main
import (
"fmt"
"github.com/rgerardi/philly-ete-2021/shape/shape"
)
type border interface { Perimeter() float64 } type square struct { shape.Rectangle } func newSquare(side float64) square { return square{shape.Rectangle{side, side}} } func main() { c := shape.Circle{5} r := shape.Rectangle{20, 10} s := newSquare(5) printBorder(c) printBorder(r) printBorder(s) }
func printBorder(b border) {
fmt.Println("Border length is", b.Perimeter())
}
package main
import (
"fmt"
"github.com/rgerardi/philly-ete-2021/shape/shape"
)
type border interface { Perimeter() float64 } type square struct { shape.Rectangle } func newSquare(side float64) square { return square{shape.Rectangle{side, side}} } func main() { shapes := make([]shape.Rectangle, 3) shapes[0] = shape.Rectangle{10, 5} shapes[1] = newSquare(5) }
func printBorder(b border) {
fmt.Println("Border length is", b.Perimeter())
}
package main
import (
"fmt"
"github.com/rgerardi/philly-ete-2021/shape/shape"
)
type border interface { Perimeter() float64 } type square struct { shape.Rectangle } func newSquare(side float64) square { return square{shape.Rectangle{side, side}} } func main() { shapes := make([]border, 3) shapes[0] = shape.Rectangle{10, 5} shapes[1] = newSquare(5) shapes[2] = shape.Circle{3} for _, s := range shapes { printBorder(s) } }
func printBorder(b border) {
fmt.Println("Border length is", b.Perimeter())
}
package shape
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius } func (r Rectangle) Area() float64 { return r.Length * r.Width }
package main
import (
"fmt"
"github.com/rgerardi/philly-ete-2021/shape/shape"
)
type border interface { Perimeter() float64 } type square struct { shape.Rectangle } func newSquare(side float64) square { return square{shape.Rectangle{side, side}} } func main() { shapes := make([]border, 3) shapes[0] = shape.Rectangle{10, 5} shapes[1] = newSquare(5) shapes[2] = shape.Circle{3} for _, s := range shapes { printBorder(s) fmt.Println(s.Area()) } }
func printBorder(b border) {
fmt.Println("Border length is", b.Perimeter())
}
package main
import (
"fmt"
"github.com/rgerardi/philly-ete-2021/shape/shape"
)
type border interface {
Perimeter() float64
}
type square struct {
shape.Rectangle
}
func newSquare(side float64) square {
return square{shape.Rectangle{side, side}}
}
type surface interface { Area() float64 } type surfaceBorder interface { border surface } func main() { shapes := make([]surfaceBorder, 3) shapes[0] = shape.Rectangle{10, 5} shapes[1] = newSquare(5) shapes[2] = shape.Circle{3} for _, s := range shapes { printBorder(s) printArea(s) } } func printArea(s surface) { fmt.Println("Surface area is", s.Area()) }
func printBorder(b border) {
fmt.Println("Border length is", b.Perimeter())
}
package main
import (
"fmt"
"github.com/rgerardi/philly-ete-2021/shape/shape"
)
type border interface {
Perimeter() float64
}
type square struct {
shape.Rectangle
}
func newSquare(side float64) square {
return square{shape.Rectangle{side, side}}
}
type surface interface {
Area() float64
}
type surfaceBorder interface {
border
surface
}
func main() { shapes := make([]surfaceBorder, 3) shapes[0] = shape.Rectangle{10, 5} shapes[1] = newSquare(5) shapes[2] = shape.Circle{3} for _, s := range shapes { printBorder(s) printArea(s) if c, ok := s.(shape.Circle); ok { fmt.Println("Radius is:", c.Radius) } } }
func printArea(s surface) {
fmt.Println("Surface area is", s.Area())
}
func printBorder(b border) {
fmt.Println("Border length is", b.Perimeter())
}
package main
import (
"fmt"
"github.com/rgerardi/philly-ete-2021/shape/shape"
)
type border interface {
Perimeter() float64
}
type square struct {
shape.Rectangle
}
func newSquare(side float64) square {
return square{shape.Rectangle{side, side}}
}
type surface interface {
Area() float64
}
type surfaceBorder interface {
border
surface
}
func main() { shapes := make([]surfaceBorder, 3) shapes[0] = shape.Rectangle{10, 5} shapes[1] = newSquare(5) shapes[2] = shape.Circle{3} for _, s := range shapes { switch c := s.(type) { case shape.Circle: fmt.Println("Radius is:", c.Radius) case shape.Rectangle: fmt.Println("Length is:", c.Length) default: fmt.Println("Not a rectangle or circle") } } }
func printArea(s surface) {
fmt.Println("Surface area is", s.Area())
}
func printBorder(b border) {
fmt.Println("Border length is", b.Perimeter())
}
"The empty interface says nothing" (Rob Pike)
37jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go
golang.org/doc/effective_go#interfaces_and_types
38pragprog.com/titles/rggo/powerful-command-line-applications-in-go/
devtalk.com/books/powerful-command-line-applications-in-go
40Ricardo Gerardi
Powerful Command-Line Applications in Go, Author
Senior Automation Consultant, Red Hat