| Stupid Gopher Tricks | |
| GolangUK | |
| 21 Aug 2015 | |
| Andrew Gerrand | |
| adg@golang.org | |
| * Video | |
| A video of this talk was recorded at GolangUK in London. | |
| .link https://www.youtube.com/watch?v=UECh7X07m6E Watch the talk on YouTube | |
| * This talk | |
| This talk is about things you might not know about Go. | |
| Some of this stuff you can add to your Go vocabulary. | |
| Other things are probably best left for special occasions. | |
| * Language | |
| * Type literals (1/2) | |
| Here's a familiar type declaration: | |
| type Foo struct { | |
| i int | |
| s string | |
| } | |
| The latter part of a type declaration is the *type*literal*: | |
| struct { | |
| i int | |
| s string | |
| } | |
| Other examples of type literals are `int` and `[]string`, | |
| which can also be declared as named types: | |
| type Bar int | |
| type Qux []string | |
| * Type literals (2/2) | |
| While we commonly use `int` and `[]string` in variable declarations: | |
| var i int | |
| var s []string | |
| It is less common (but equally valid) to do the same with structs: | |
| var t struct { | |
| i int | |
| s string | |
| } | |
| An unnamed struct literal is often called an *anonymous*struct*. | |
| * Anonymous structs: template data | |
| A common use is providing data to templates: | |
| .play tricks/template.go /BEGIN/,/END/ | |
| You could also use `map[string]interface{}`, | |
| but then you sacrifice performance and type safety. | |
| * Anonymous structs: JSON (1/2) | |
| The same technique can be used for encoding JSON objects: | |
| .play tricks/json-encode.go /Marshal/,/Print/ | |
| And also decoding: | |
| .play tricks/json-decode.go /data/,/Print/ | |
| * Anonymous structs: JSON (2/2) | |
| Structs can be nested to describe more complex JSON objects: | |
| .play tricks/json-nest.go /data/,/Print/ | |
| * Repeated literals and struct names | |
| In repeated literals like slices and maps, Go lets you omit the inner type name: | |
| .code tricks/repeated.go /BEGIN/,/END/ | |
| * Repeated literals and anonymous structs | |
| Combined with anonymous structs, this convenience shortens the code dramatically: | |
| .code tricks/repeated2.go /BEGIN/,/END/ | |
| * Anonymous structs: test cases (1/2) | |
| These properties enable a nice way to express test cases: | |
| .code tricks/string_test.go /TestIndex/,/^}/ | |
| * Anonymous structs: test cases (2/2) | |
| You can go a step further and put the composite literal in the range statement itself: | |
| .code tricks/string_test2.go /TestIndex/,/^}/ | |
| But this is harder to read. | |
| * Embedded fields | |
| A struct field that has no name is an *embedded*field*. | |
| The embedded type's methods (and fields, if it is a struct) | |
| are accessible as if they are part of the embedding struct. | |
| .play tricks/embed.go /BEGIN/,$ | |
| * Anonymous structs: embedded mutex | |
| Of course, you can embed fields in an anonymous struct. | |
| It's common to protect a global variable with a mutex variable: | |
| var ( | |
| viewCount int64 | |
| viewCountMu sync.Mutex | |
| ) | |
| By embedding a mutex in an anonymous struct, we can group the related values: | |
| var viewCount struct { | |
| sync.Mutex | |
| n int64 | |
| } | |
| Users of `viewCount` access it like this: | |
| viewCount.Lock() | |
| viewCount.n++ | |
| viewCount.Unlock() | |
| * Anonymous structs: implementing interfaces | |
| And you can embed interfaces, too. | |
| Here's a real example from [[https://camlistore.org/][Camlistore]]: | |
| The function is expected to return a `ReadSeekCloser`, | |
| but the programmer only had a string. | |
| Anonymous struct (and its standard library friends) to the rescue! | |
| return struct { | |
| io.ReadSeeker | |
| io.Closer | |
| }{ | |
| io.NewSectionReader(strings.NewReader(s), 0, int64(len(s))), | |
| ioutil.NopCloser(nil), | |
| } | |
| * Anonymous interfaces | |
| Interfaces can be anonymous, the most common being `interface{}`. | |
| But the interface needn't be empty: | |
| .play tricks/anon-interface.go /var s/,/Println/ | |
| Useful for a sly type assertion (from `src/os/exec/exec_test.go`): | |
| // Check that we can access methods of the underlying os.File. | |
| if _, ok := stdin.(interface { | |
| Fd() uintptr | |
| }); !ok { | |
| t.Error("can't access methods of underlying *os.File") | |
| } | |
| * Method values | |
| A "method value" is what you get when you evaluate a method as an expression. | |
| The result is a function value. | |
| Evaluating a method from a _type_ yields a function: | |
| .play tricks/method-values-1.go /var f/,/Stdout/ | |
| Evaluating a method from a _value_ creates a closure that holds that value: | |
| .play tricks/method-values-2.go /var /,/Stdout/ | |
| * Method values: sync.Once | |
| The `sync.Once` type is used to perform a task once with concurrency safety. | |
| // Once is an object that will perform exactly one action. | |
| type Once struct { /* Has unexported fields. */ } | |
| func (o *Once) Do(f func()) | |
| This `LazyPrimes` type computes a slice of prime numbers the first time it is used: | |
| .code tricks/method-once.go /type/,$ | |
| * Method values: HTTP handlers | |
| You can use method values to implement multiple HTTP handlers with one type: | |
| .code tricks/method-http.go /type Server/,$ | |
| * Method values: another example | |
| In package `os/exec`, the `Cmd` type implements methods to set up | |
| standard input, output, and error: | |
| func (c *Cmd) stdin() (f *os.File, err error) | |
| func (c *Cmd) stdout() (f *os.File, err error) | |
| func (c *Cmd) stderr() (f *os.File, err error) | |
| The caller handles each in the same way, | |
| so it iterates over a slice of method values: | |
| type F func(*Cmd) (*os.File, error) | |
| for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} { | |
| fd, err := setupFd(c) | |
| if err != nil { | |
| c.closeDescriptors(c.closeAfterStart) | |
| c.closeDescriptors(c.closeAfterWait) | |
| return err | |
| } | |
| c.childFiles = append(c.childFiles, fd) | |
| } | |
| * Comparable types | |
| The Go spec defines a set of types as "comparable"; | |
| they may be compared with == and !=. | |
| Bools, ints, floats, complex numbers, strings, pointers, | |
| channels, *structs*, and *interfaces* are comparable. | |
| .play tricks/compare.go /BEGIN/,/END/ | |
| A struct is comparable only if its fields are comparable: | |
| .play tricks/compare2.go /BEGIN/,/END/ | |
| * Comparable types and map keys | |
| Any comparable type may be used as a map key. | |
| .code tricks/compare-map.go /BEGIN/,/END/ | |
| * Structs as map keys | |
| An example from the Go continuous build infrastructure: | |
| type builderRev struct { | |
| builder, rev string | |
| } | |
| var br = builderRev{"linux-amd64", "0cd299"} | |
| We track in-flight builds in a map. | |
| The pre-Go 1 way was to flatten the data to a string first: | |
| inflight := map[string]bool{} | |
| inflight[br.builder + "-" + br.rev] = true | |
| But with struct keys, you can avoid the allocation and have cleaner code: | |
| inflight := map[builderRev]bool{} | |
| inflight[br] = true | |
| * Interfaces as map keys | |
| An example of interface map keys from Docker's `broadcastwriter` package: | |
| .code tricks/broadcastwriter/broadcastwriter.go /type/,/END/ | |
| * Structs and interfaces together as map keys | |
| A (very) contrived example: (Don't do this! Ever!) | |
| .play tricks/cons.go /type/,$ | |
| * Libraries | |
| * sync/atomic | |
| For a simple counter, you can use the `sync/atomic` package's | |
| functions to make atomic updates without the lock. | |
| func AddInt64(addr *int64, delta int64) (new int64) | |
| func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool) | |
| func LoadInt64(addr *int64) (val int64) | |
| func StoreInt64(addr *int64, val int64) | |
| func SwapInt64(addr *int64, new int64) (old int64) | |
| First, define the global variable (appropriately documented!): | |
| // viewCount must be updated atomically. | |
| var viewCount int64 | |
| Then increment it with `AddInt64`: | |
| count := atomic.AddInt64(&viewCount, 1) | |
| The set are available for `Int32`, `Uint32`, `Int64`, `Uint64`, `Pointer`, and `Uintptr`. | |
| * sync/atomic.Value | |
| Another option for sharing state is `atomic.Value`. | |
| For instance, to share configuration between many goroutines: | |
| type Config struct { | |
| Timeout time.Duration | |
| } | |
| var config atomic.Value | |
| To set or update, use the `Store` method: | |
| config.Store(&Config{Timeout: 2*time.Second}) | |
| To read, each goroutine calls the `Load` method: | |
| cfg := config.Load().(*Config) | |
| Note that storing different types in the same `Value` will cause a panic. | |
| * sync/atomic.Value: how it works (1/5) | |
| The `atomic.Value` primitive is the size of a single interface value: | |
| package atomic | |
| type Value struct { | |
| v interface{} | |
| } | |
| An interface value is represented by the runtime as two pointers: | |
| one for the type, and one for the value. | |
| // ifaceWords is interface{} internal representation. | |
| type ifaceWords struct { | |
| typ unsafe.Pointer | |
| data unsafe.Pointer | |
| } | |
| To load and store an interface value atomically, it operates on the parts of the interface value with `atomic.LoadPointer` and `atomic.StorePointer`. | |
| * sync/atomic.Value: how it works (2/5) | |
| The `Store` method first validates the input: | |
| func (v *Value) Store(x interface{}) { | |
| if x == nil { | |
| panic("sync/atomic: store of nil value into Value") | |
| } | |
| Then uses `unsafe` to cast the current and new `interface{}` values to `ifaceWords`: | |
| // ... | |
| vp := (*ifaceWords)(unsafe.Pointer(v)) | |
| xp := (*ifaceWords)(unsafe.Pointer(&x)) | |
| (This allows us to get at the internals of those interface values.) | |
| * sync/atomic.Value: how it works (3/5) | |
| Spin while loading the type field: | |
| // ... | |
| for { | |
| typ := LoadPointer(&vp.typ) | |
| If it's `nil` the this is the first time the value has been stored. | |
| Put a sentinel value (max uintptr) in the type field to "lock" it while we work with it: | |
| // ... | |
| if typ == nil { | |
| if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) { | |
| continue // Someone beat us to it. Wait. | |
| } | |
| Store the data field, then the type field, and we're done! | |
| // ... | |
| StorePointer(&vp.data, xp.data) | |
| StorePointer(&vp.typ, xp.typ) | |
| return | |
| } | |
| * sync/atomic.Value: how it works (4/5) | |
| If this isn't the first store, check whether a store is already happening: | |
| // ... | |
| if uintptr(typ) == ^uintptr(0) { | |
| continue // First store in progress. Wait. | |
| } | |
| Sanity check whether the type changed: | |
| // ... | |
| if typ != xp.typ { | |
| panic("sync/atomic: store of inconsistently typed value into Value") | |
| } | |
| If the type field is what we expect, go ahead and atomically store the value: | |
| // ... | |
| StorePointer(&vp.data, xp.data) | |
| return | |
| } | |
| } | |
| * sync/atomic.Value: how it works (5/5) | |
| The `Load` method first loads the interface's type field: | |
| func (v *Value) Load() (x interface{}) { | |
| vp := (*ifaceWords)(unsafe.Pointer(v)) | |
| typ := LoadPointer(&vp.typ) | |
| Then, check whether a store has happened, or is happening: | |
| // ... | |
| if typ == nil || uintptr(typ) == ^uintptr(0) { | |
| return nil | |
| } | |
| Otherwise, load the data field and return both type and data as a new interface value: | |
| // ... | |
| data := LoadPointer(&vp.data) | |
| xp := (*ifaceWords)(unsafe.Pointer(&x)) | |
| xp.typ = typ | |
| xp.data = data | |
| return | |
| } | |
| * A note on sync/atomic | |
| Usually you don't want or need the stuff in this package. | |
| Try channels or the `sync` package first. | |
| You almost certainly shouldn't write code like the `atomic.Value` implementation. | |
| (And I didn't show all of it; the real code has hooks into the runtime.) | |
| * Tools | |
| * Testing | |
| * Subprocess tests | |
| Sometimes you need to test the behavior of a process, not just a function. | |
| .code tricks/subprocess/subprocess.go /func Crasher/,/^}/ | |
| To test this code, we invoke the test binary itself as a subprocess: | |
| .code tricks/subprocess/subprocess_test.go /func TestCrasher/,/^}/ | |
| * Subprocess benchmarks (1/2) | |
| Go's CPU and memory profilers report data for an entire process. | |
| To profile just one side of a concurrent operation, you can use a sub-process. | |
| This benchmark from the `net/http` package spawns a child process to make | |
| requests to a server running in the main process. | |
| func BenchmarkServer(b *testing.B) { | |
| b.ReportAllocs() | |
| // Child process mode; | |
| if url := os.Getenv("TEST_BENCH_SERVER_URL"); url != "" { | |
| n, err := strconv.Atoi(os.Getenv("TEST_BENCH_CLIENT_N")) | |
| if err != nil { | |
| panic(err) | |
| } | |
| for i := 0; i < n; i++ { | |
| res, err := Get(url) | |
| // ... | |
| } | |
| os.Exit(0) | |
| return | |
| } | |
| // ... | |
| * Subprocess benchmarks (2/2) | |
| // ... | |
| res := []byte("Hello world.\n") | |
| ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) { | |
| rw.Header().Set("Content-Type", "text/html; charset=utf-8") | |
| rw.Write(res) | |
| })) | |
| defer ts.Close() | |
| cmd := exec.Command(os.Args[0], "-test.run=XXXX", "-test.bench=BenchmarkServer$") | |
| cmd.Env = append([]string{ | |
| fmt.Sprintf("TEST_BENCH_CLIENT_N=%d", b.N), | |
| fmt.Sprintf("TEST_BENCH_SERVER_URL=%s", ts.URL), | |
| }, os.Environ()...) | |
| if out, err := cmd.CombinedOutput(); err != nil { | |
| b.Errorf("Test failure: %v, with output: %s", err, out) | |
| } | |
| } | |
| To run: | |
| $ go test -run=XX -bench=BenchmarkServer -benchtime=15s -cpuprofile=http.prof | |
| $ go tool pprof http.test http.prof | |
| * go list | |
| * go list | |
| $ go help list | |
| usage: go list [-e] [-f format] [-json] [build flags] [packages] | |
| List lists the packages named by the import paths, one per line. | |
| Show the packages under a path: | |
| $ go list golang.org/x/oauth2/... | |
| golang.org/x/oauth2 | |
| ... | |
| golang.org/x/oauth2/vk | |
| Show the standard library: | |
| $ go list std | |
| archive/tar | |
| ... | |
| unsafe | |
| Show all packages: | |
| $ go list all | |
| * go list -json | |
| The `-json` flag tells you everything the go tool knows about a package: | |
| $ go list -json bytes | |
| { | |
| "Dir": "/Users/adg/go/src/bytes", | |
| "ImportPath": "bytes", | |
| "Name": "bytes", | |
| "Doc": "Package bytes implements functions for the manipulation of byte slices.", | |
| "Target": "/Users/adg/go/pkg/darwin_amd64/bytes.a", | |
| "Goroot": true, | |
| "Standard": true, | |
| "Stale": true, | |
| "Root": "/Users/adg/go", | |
| "GoFiles": [ | |
| "buffer.go", | |
| "bytes.go", | |
| "bytes_decl.go", | |
| "reader.go" | |
| ], | |
| ... | |
| } | |
| It's easy to write programs that consume this data. | |
| * go list's Package struct (1/3) | |
| The go tool's documented `Package` struct describes all the possible fields: | |
| type Package struct { | |
| Dir string // directory containing package sources | |
| ImportPath string // import path of package in dir | |
| ImportComment string // path in import comment on package statement | |
| Name string // package name | |
| Doc string // package documentation string | |
| Target string // install path | |
| Shlib string // the shared library that contains this package (only set when -linkshared) | |
| Goroot bool // is this package in the Go root? | |
| Standard bool // is this package part of the standard Go library? | |
| Stale bool // would 'go install' do anything for this package? | |
| Root string // Go root or Go path dir containing this package | |
| // (more fields on next slide) | |
| } | |
| * go list's Package struct (2/3) | |
| type Package struct { | |
| // (more fields on previous slide) | |
| // Source files | |
| GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) | |
| CgoFiles []string // .go sources files that import "C" | |
| IgnoredGoFiles []string // .go sources ignored due to build constraints | |
| CFiles []string // .c source files | |
| CXXFiles []string // .cc, .cxx and .cpp source files | |
| MFiles []string // .m source files | |
| HFiles []string // .h, .hh, .hpp and .hxx source files | |
| SFiles []string // .s source files | |
| SwigFiles []string // .swig files | |
| SwigCXXFiles []string // .swigcxx files | |
| SysoFiles []string // .syso object files to add to archive | |
| // (more fields on next slide) | |
| } | |
| * go list's Package struct (3/3) | |
| type Package struct { | |
| // (more fields on previous slide) | |
| // Cgo directives | |
| CgoCFLAGS []string // cgo: flags for C compiler | |
| CgoCPPFLAGS []string // cgo: flags for C preprocessor | |
| CgoCXXFLAGS []string // cgo: flags for C++ compiler | |
| CgoLDFLAGS []string // cgo: flags for linker | |
| CgoPkgConfig []string // cgo: pkg-config names | |
| // Dependency information | |
| Imports []string // import paths used by this package | |
| Deps []string // all (recursively) imported dependencies | |
| // Error information | |
| Incomplete bool // this package or a dependency has an error | |
| Error *PackageError // error loading package | |
| DepsErrors []*PackageError // errors loading dependencies | |
| TestGoFiles []string // _test.go files in package | |
| TestImports []string // imports from TestGoFiles | |
| XTestGoFiles []string // _test.go files outside package | |
| XTestImports []string // imports from XTestGoFiles | |
| } | |
| * go list -f (1/2) | |
| That's a ton of information, so what can we do with it? | |
| The `-f` flag lets you use Go's `text/template` package to format the ouput. | |
| Show package doc strings: | |
| $ go list -f '{{.Doc}}' golang.org/x/oauth2/... | |
| Package oauth2 provides support for making OAuth2 authorized and authenticated HTTP requests. | |
| Package jwt implements the OAuth 2.0 JSON Web Token flow, commonly known as "two-legged OAuth 2.0". | |
| ... | |
| Show the Go files in a package: | |
| $ go list -f '{{.GoFiles}}' bytes | |
| [buffer.go bytes.go bytes_decl.go reader.go] | |
| Make the output cleaner with a `join`: | |
| $ go list -f '{{join .GoFiles " "}}' bytes | |
| buffer.go bytes.go bytes_decl.go reader.go | |
| * go list -f (2/2) | |
| With template logic we can test packages for certain conditions. | |
| Find standard libraries that lack documentation: | |
| $ go list -f '{{if not .Doc}}{{.ImportPath}}{{end}}' std | |
| internal/format | |
| internal/trace | |
| Find packages that depend (directly or indirectly) on a given package: | |
| $ go list -f '{{range .Deps}}{{if eq . "golang.org/x/oauth2"}}{{$.ImportPath}}{{end}}{{end}}' all | |
| golang.org/x/build/auth | |
| golang.org/x/build/buildlet | |
| golang.org/x/build/cmd/buildlet | |
| ... | |
| Find packages that are broken somehow (note `-e`): | |
| $ go list -e -f '{{with .Error}}{{.}}{{end}}' all | |
| package github.com/golang/oauth2: code in directory /Users/adg/src/github.com/golang/oauth2 | |
| expects import "golang.org/x/oauth2" | |
| ... | |
| * go list and the shell | |
| Things get interesting once we start using the shell. | |
| For instance, we can use `go` `list` output as input to the go tool. | |
| Test all packages except vendored packages: | |
| $ go test $(go list ./... | grep -v '/vendor/') | |
| Test the dependencies of a specific package: | |
| $ go test $(go list -f '{{join .Deps " "}}' golang.org/x/oauth2) | |
| The same, but don't test the standard library: | |
| $ go test $( | |
| go list -f '{{if (not .Goroot)}}{{.ImportPath}}{{end}}' $( | |
| go list -f '{{join .Deps " "}}' golang.org/x/oauth2 | |
| ) | |
| ) | |
| * go list printing line counts | |
| for pkg in $(go list golang.org/x/oauth2/...); do | |
| wc -l $(go list -f '{{range .GoFiles}}{{$.Dir}}/{{.}} {{end}}' $pkg) | \ | |
| tail -1 | awk '{ print $1 " '$pkg'" }' | |
| done | sort -nr | |
| The output: | |
| 617 golang.org/x/oauth2/google | |
| 600 golang.org/x/oauth2 | |
| 357 golang.org/x/oauth2/internal | |
| 160 golang.org/x/oauth2/jws | |
| 147 golang.org/x/oauth2/jwt | |
| 112 golang.org/x/oauth2/clientcredentials | |
| 22 golang.org/x/oauth2/paypal | |
| 16 golang.org/x/oauth2/vk | |
| 16 golang.org/x/oauth2/odnoklassniki | |
| 16 golang.org/x/oauth2/linkedin | |
| 16 golang.org/x/oauth2/github | |
| 16 golang.org/x/oauth2/facebook | |
| * go list generating dependency graphs | |
| ( echo "digraph G {" | |
| go list -f '{{range .Imports}}{{printf "\t%q -> %q;\n" $.ImportPath .}}{{end}}' \ | |
| $(go list -f '{{join .Deps " "}}' time) time | |
| echo "}" | |
| ) | dot -Tsvg -o time-deps.svg | |
| .image tricks/time-deps.png 400 _ | |
| * And there's more! | |
| There are many more fun corners of Go. | |
| Can you find them all? :-) | |
| Read the docs, explore, and have fun! |