github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/talks/2015/tricks.slide (about)

     1  Stupid Gopher Tricks
     2  GolangUK
     3  21 Aug 2015
     4  
     5  Andrew Gerrand
     6  adg@golang.org
     7  
     8  
     9  * Video
    10  
    11  A video of this talk was recorded at GolangUK in London.
    12  
    13  .link https://www.youtube.com/watch?v=UECh7X07m6E Watch the talk on YouTube
    14  
    15  
    16  * This talk
    17  
    18  This talk is about things you might not know about Go.
    19  
    20  Some of this stuff you can add to your Go vocabulary.
    21  
    22  Other things are probably best left for special occasions.
    23  
    24  
    25  * Language
    26  
    27  
    28  * Type literals (1/2)
    29  
    30  Here's a familiar type declaration:
    31  
    32  	type Foo struct {
    33  		i int
    34  		s string
    35  	}
    36  
    37  The latter part of a type declaration is the *type*literal*:
    38  
    39  	struct {
    40  		i int
    41  		s string
    42  	}
    43  
    44  Other examples of type literals are `int` and `[]string`,
    45  which can also be declared as named types:
    46  
    47  	type Bar int
    48  	type Qux []string
    49  
    50  
    51  * Type literals (2/2)
    52  
    53  While we commonly use `int` and `[]string` in variable declarations:
    54  
    55  	var i int
    56  	var s []string
    57  
    58  It is less common (but equally valid) to do the same with structs:
    59  
    60  	var t struct {
    61  		i int
    62  		s string
    63  	}
    64  
    65  An unnamed struct literal is often called an *anonymous*struct*.
    66  
    67  
    68  * Anonymous structs: template data
    69  
    70  A common use is providing data to templates:
    71  
    72  .play tricks/template.go /BEGIN/,/END/
    73  
    74  You could also use `map[string]interface{}`,
    75  but then you sacrifice performance and type safety.
    76  
    77  
    78  * Anonymous structs: JSON (1/2)
    79  
    80  The same technique can be used for encoding JSON objects:
    81  
    82  .play tricks/json-encode.go /Marshal/,/Print/
    83  
    84  And also decoding:
    85  
    86  .play tricks/json-decode.go /data/,/Print/
    87  
    88  
    89  * Anonymous structs: JSON (2/2)
    90  
    91  Structs can be nested to describe more complex JSON objects:
    92  
    93  .play tricks/json-nest.go /data/,/Print/
    94  
    95  
    96  * Repeated literals and struct names
    97  
    98  In repeated literals like slices and maps, Go lets you omit the inner type name:
    99  
   100  .code tricks/repeated.go /BEGIN/,/END/
   101  
   102  
   103  * Repeated literals and anonymous structs
   104  
   105  Combined with anonymous structs, this convenience shortens the code dramatically:
   106  
   107  .code tricks/repeated2.go /BEGIN/,/END/
   108  
   109  
   110  * Anonymous structs: test cases (1/2)
   111  
   112  These properties enable a nice way to express test cases:
   113  
   114  .code tricks/string_test.go /TestIndex/,/^}/
   115  
   116  
   117  * Anonymous structs: test cases (2/2)
   118  
   119  You can go a step further and put the composite literal in the range statement itself:
   120  
   121  .code tricks/string_test2.go /TestIndex/,/^}/
   122  
   123  But this is harder to read.
   124  
   125  
   126  * Embedded fields
   127  
   128  A struct field that has no name is an *embedded*field*.
   129  The embedded type's methods (and fields, if it is a struct)
   130  are accessible as if they are part of the embedding struct.
   131  
   132  .play tricks/embed.go /BEGIN/,$
   133  
   134  
   135  * Anonymous structs: embedded mutex
   136  
   137  Of course, you can embed fields in an anonymous struct.
   138  
   139  It's common to protect a global variable with a mutex variable:
   140  
   141  	var (
   142  		viewCount   int64
   143  		viewCountMu sync.Mutex
   144  	)
   145  
   146  By embedding a mutex in an anonymous struct, we can group the related values:
   147  
   148  	var viewCount struct {
   149  		sync.Mutex
   150  		n int64
   151  	}
   152  
   153  Users of `viewCount` access it like this:
   154  
   155  	viewCount.Lock()
   156  	viewCount.n++
   157  	viewCount.Unlock()
   158  
   159  
   160  
   161  * Anonymous structs: implementing interfaces
   162  
   163  And you can embed interfaces, too.
   164  
   165  Here's a real example from [[https://camlistore.org/][Camlistore]]:
   166  
   167  The function is expected to return a `ReadSeekCloser`,
   168  but the programmer only had a string.
   169  
   170  Anonymous struct (and its standard library friends) to the rescue!
   171  
   172  	return struct {
   173  		io.ReadSeeker
   174  		io.Closer
   175  	}{
   176  		io.NewSectionReader(strings.NewReader(s), 0, int64(len(s))),
   177  		ioutil.NopCloser(nil),
   178  	}
   179  
   180  
   181  * Anonymous interfaces
   182  
   183  Interfaces can be anonymous, the most common being `interface{}`.
   184  
   185  But the interface needn't be empty:
   186  
   187  .play tricks/anon-interface.go /var s/,/Println/
   188  
   189  Useful for a sly type assertion (from `src/os/exec/exec_test.go`):
   190  
   191  	// Check that we can access methods of the underlying os.File.
   192  	if _, ok := stdin.(interface {
   193  		Fd() uintptr
   194  	}); !ok {
   195  		t.Error("can't access methods of underlying *os.File")
   196  	}
   197  
   198  
   199  * Method values
   200  
   201  A "method value" is what you get when you evaluate a method as an expression.
   202  The result is a function value.
   203  
   204  Evaluating a method from a _type_ yields a function:
   205  
   206  .play tricks/method-values-1.go /var f/,/Stdout/
   207  
   208  Evaluating a method from a _value_ creates a closure that holds that value:
   209  
   210  .play tricks/method-values-2.go /var /,/Stdout/
   211  
   212  
   213  * Method values: sync.Once
   214  
   215  The `sync.Once` type is used to perform a task once with concurrency safety.
   216  
   217  	// Once is an object that will perform exactly one action.
   218  	type Once struct { /* Has unexported fields. */ }
   219  
   220  	func (o *Once) Do(f func())
   221  	
   222  This `LazyPrimes` type computes a slice of prime numbers the first time it is used:
   223  
   224  .code tricks/method-once.go /type/,$
   225  
   226  
   227  * Method values: HTTP handlers
   228  
   229  You can use method values to implement multiple HTTP handlers with one type:
   230  
   231  .code tricks/method-http.go /type Server/,$
   232  
   233  
   234  * Method values: another example
   235  
   236  In package `os/exec`, the `Cmd` type implements methods to set up
   237  standard input, output, and error:
   238  
   239  	func (c *Cmd) stdin() (f *os.File, err error)
   240  	func (c *Cmd) stdout() (f *os.File, err error)
   241  	func (c *Cmd) stderr() (f *os.File, err error)
   242  
   243  The caller handles each in the same way,
   244  so it iterates over a slice of method values:
   245  
   246  	type F func(*Cmd) (*os.File, error)
   247  	for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} {
   248  		fd, err := setupFd(c)
   249  		if err != nil {
   250  			c.closeDescriptors(c.closeAfterStart)
   251  			c.closeDescriptors(c.closeAfterWait)
   252  			return err
   253  		}
   254  		c.childFiles = append(c.childFiles, fd)
   255  	}
   256  
   257  
   258  * Comparable types
   259  
   260  The Go spec defines a set of types as "comparable";
   261  they may be compared with == and !=.
   262  
   263  Bools, ints, floats, complex numbers, strings, pointers,
   264  channels, *structs*, and *interfaces* are comparable.
   265  
   266  .play tricks/compare.go /BEGIN/,/END/
   267  
   268  A struct is comparable only if its fields are comparable:
   269  
   270  .play tricks/compare2.go /BEGIN/,/END/
   271  
   272  
   273  * Comparable types and map keys
   274  
   275  Any comparable type may be used as a map key.
   276  
   277  .code tricks/compare-map.go /BEGIN/,/END/
   278  
   279  
   280  * Structs as map keys
   281  
   282  An example from the Go continuous build infrastructure:
   283  
   284  	type builderRev struct {
   285  		builder, rev string
   286  	}
   287  
   288  	var br = builderRev{"linux-amd64", "0cd299"}
   289  
   290  We track in-flight builds in a map.
   291  The pre-Go 1 way was to flatten the data to a string first:
   292  
   293  	inflight := map[string]bool{}
   294  
   295  	inflight[br.builder + "-" + br.rev] = true
   296  
   297  But with struct keys, you can avoid the allocation and have cleaner code:
   298  
   299  	inflight := map[builderRev]bool{}
   300  
   301  	inflight[br] = true
   302  
   303  
   304  * Interfaces as map keys
   305  
   306  An example of interface map keys from Docker's `broadcastwriter` package:
   307  
   308  .code tricks/broadcastwriter/broadcastwriter.go /type/,/END/
   309  
   310  
   311  * Structs and interfaces together as map keys
   312  
   313  A (very) contrived example: (Don't do this! Ever!)
   314  
   315  .play tricks/cons.go /type/,$
   316  
   317  
   318  * Libraries
   319  
   320  * sync/atomic
   321  
   322  For a simple counter, you can use the `sync/atomic` package's
   323  functions to make atomic updates without the lock.
   324  
   325  	func AddInt64(addr *int64, delta int64) (new int64)
   326  	func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
   327  	func LoadInt64(addr *int64) (val int64)
   328  	func StoreInt64(addr *int64, val int64)
   329  	func SwapInt64(addr *int64, new int64) (old int64)
   330  
   331  First, define the global variable (appropriately documented!):
   332  
   333  	// viewCount must be updated atomically.
   334  	var viewCount int64
   335  
   336  Then increment it with `AddInt64`:
   337  
   338  	count := atomic.AddInt64(&viewCount, 1)
   339  
   340  The set are available for `Int32`, `Uint32`, `Int64`, `Uint64`, `Pointer`, and `Uintptr`.
   341  
   342  
   343  * sync/atomic.Value
   344  
   345  Another option for sharing state is `atomic.Value`.
   346  
   347  For instance, to share configuration between many goroutines:
   348  
   349  	type Config struct {
   350  		Timeout time.Duration
   351  	}
   352  
   353  	var config atomic.Value
   354  
   355  To set or update, use the `Store` method:
   356  	
   357  	config.Store(&Config{Timeout: 2*time.Second})
   358  
   359  To read, each goroutine calls the `Load` method:
   360  
   361  	cfg := config.Load().(*Config)
   362  
   363  Note that storing different types in the same `Value` will cause a panic.
   364  
   365  
   366  * sync/atomic.Value: how it works (1/5)
   367  
   368  The `atomic.Value` primitive is the size of a single interface value:
   369  
   370  	package atomic
   371  
   372  	type Value struct {
   373  		v interface{}
   374  	}
   375  
   376  An interface value is represented by the runtime as two pointers:
   377  one for the type, and one for the value.
   378  
   379  	// ifaceWords is interface{} internal representation.
   380  	type ifaceWords struct {
   381  		typ  unsafe.Pointer
   382  		data unsafe.Pointer
   383  	}
   384  
   385  To load and store an interface value atomically, it operates on the parts of the interface value with `atomic.LoadPointer` and `atomic.StorePointer`.
   386  
   387  
   388  * sync/atomic.Value: how it works (2/5)
   389  
   390  The `Store` method first validates the input:
   391  
   392  	func (v *Value) Store(x interface{}) {
   393  		if x == nil {
   394  			panic("sync/atomic: store of nil value into Value")
   395  		}
   396  
   397  Then uses `unsafe` to cast the current and new `interface{}` values to `ifaceWords`:
   398  
   399  	// ...
   400  		vp := (*ifaceWords)(unsafe.Pointer(v))
   401  		xp := (*ifaceWords)(unsafe.Pointer(&x))
   402  
   403  (This allows us to get at the internals of those interface values.)
   404  
   405  
   406  * sync/atomic.Value: how it works (3/5)
   407  
   408  Spin while loading the type field:
   409  
   410  	// ...
   411  		for {
   412  			typ := LoadPointer(&vp.typ)
   413  
   414  If it's `nil` the this is the first time the value has been stored.
   415  Put a sentinel value (max uintptr) in the type field to "lock" it while we work with it:
   416  
   417  	// ...
   418  			if typ == nil {
   419  				if !CompareAndSwapPointer(&vp.typ, nil, unsafe.Pointer(^uintptr(0))) {
   420  					continue // Someone beat us to it. Wait.
   421  				}
   422  
   423  Store the data field, then the type field, and we're done!
   424  
   425  	// ...
   426  				StorePointer(&vp.data, xp.data)
   427  				StorePointer(&vp.typ, xp.typ)
   428  				return
   429  			}
   430  
   431  * sync/atomic.Value: how it works (4/5)
   432  
   433  If this isn't the first store, check whether a store is already happening:
   434  
   435  	// ...
   436  			if uintptr(typ) == ^uintptr(0) {
   437  				continue // First store in progress. Wait.
   438  			}
   439  
   440  Sanity check whether the type changed:
   441  
   442  	// ...
   443  			if typ != xp.typ {
   444  				panic("sync/atomic: store of inconsistently typed value into Value")
   445  			}
   446  
   447  If the type field is what we expect, go ahead and atomically store the value:
   448  
   449  	// ...
   450  			StorePointer(&vp.data, xp.data)
   451  			return
   452  		}
   453  	}
   454  
   455  * sync/atomic.Value: how it works (5/5)
   456  
   457  The `Load` method first loads the interface's type field:
   458  
   459  	func (v *Value) Load() (x interface{}) {
   460  		vp := (*ifaceWords)(unsafe.Pointer(v))
   461  		typ := LoadPointer(&vp.typ)
   462  
   463  Then, check whether a store has happened, or is happening:
   464  
   465  	// ...
   466  		if typ == nil || uintptr(typ) == ^uintptr(0) {
   467  			return nil
   468  		}
   469  
   470  Otherwise, load the data field and return both type and data as a new interface value:
   471  
   472  	// ...
   473  		data := LoadPointer(&vp.data)
   474  		xp := (*ifaceWords)(unsafe.Pointer(&x))
   475  		xp.typ = typ
   476  		xp.data = data
   477  		return
   478  	}
   479  
   480  
   481  * A note on sync/atomic
   482  
   483  Usually you don't want or need the stuff in this package.
   484  
   485  Try channels or the `sync` package first.
   486  
   487  You almost certainly shouldn't write code like the `atomic.Value` implementation.
   488  (And I didn't show all of it; the real code has hooks into the runtime.)
   489  
   490  
   491  * Tools
   492  
   493  
   494  * Testing
   495  
   496  * Subprocess tests
   497  
   498  Sometimes you need to test the behavior of a process, not just a function.
   499  
   500  .code tricks/subprocess/subprocess.go /func Crasher/,/^}/
   501  
   502  To test this code, we invoke the test binary itself as a subprocess:
   503  
   504  .code tricks/subprocess/subprocess_test.go /func TestCrasher/,/^}/
   505  
   506  
   507  * Subprocess benchmarks (1/2)
   508  
   509  Go's CPU and memory profilers report data for an entire process.
   510  To profile just one side of a concurrent operation, you can use a sub-process.
   511  
   512  This benchmark from the `net/http` package spawns a child process to make
   513  requests to a server running in the main process.
   514  
   515  	func BenchmarkServer(b *testing.B) {
   516  		b.ReportAllocs()
   517  		// Child process mode;
   518  		if url := os.Getenv("TEST_BENCH_SERVER_URL"); url != "" {
   519  			n, err := strconv.Atoi(os.Getenv("TEST_BENCH_CLIENT_N"))
   520  			if err != nil {
   521  				panic(err)
   522  			}
   523  			for i := 0; i < n; i++ {
   524  				res, err := Get(url)
   525  				// ...
   526  			}
   527  			os.Exit(0)
   528  			return
   529  		}
   530  	// ...
   531  
   532  * Subprocess benchmarks (2/2)
   533  
   534  	// ...
   535  		res := []byte("Hello world.\n")
   536  		ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) {
   537  			rw.Header().Set("Content-Type", "text/html; charset=utf-8")
   538  			rw.Write(res)
   539  		}))
   540  		defer ts.Close()
   541  
   542  		cmd := exec.Command(os.Args[0], "-test.run=XXXX", "-test.bench=BenchmarkServer$")
   543  		cmd.Env = append([]string{
   544  			fmt.Sprintf("TEST_BENCH_CLIENT_N=%d", b.N),
   545  			fmt.Sprintf("TEST_BENCH_SERVER_URL=%s", ts.URL),
   546  		}, os.Environ()...)
   547  		if out, err := cmd.CombinedOutput(); err != nil {
   548  			b.Errorf("Test failure: %v, with output: %s", err, out)
   549  		}
   550  	}
   551  
   552  To run:
   553  
   554  	$ go test -run=XX -bench=BenchmarkServer -benchtime=15s -cpuprofile=http.prof
   555  	$ go tool pprof http.test http.prof
   556  
   557  
   558  * go list
   559  
   560  * go list
   561  
   562  	$ go help list
   563  	usage: go list [-e] [-f format] [-json] [build flags] [packages]
   564  
   565  	List lists the packages named by the import paths, one per line.
   566  
   567  Show the packages under a path:
   568  
   569  	$ go list golang.org/x/oauth2/...
   570  	golang.org/x/oauth2
   571  	...
   572  	golang.org/x/oauth2/vk
   573  
   574  Show the standard library:
   575  
   576  	$ go list std
   577  	archive/tar
   578  	...
   579  	unsafe
   580  
   581  Show all packages:
   582  
   583  	$ go list all
   584  
   585  * go list -json
   586  
   587  The `-json` flag tells you everything the go tool knows about a package:
   588  
   589  	$ go list -json bytes
   590  	{
   591  		"Dir": "/Users/adg/go/src/bytes",
   592  		"ImportPath": "bytes",
   593  		"Name": "bytes",
   594  		"Doc": "Package bytes implements functions for the manipulation of byte slices.",
   595  		"Target": "/Users/adg/go/pkg/darwin_amd64/bytes.a",
   596  		"Goroot": true,
   597  		"Standard": true,
   598  		"Stale": true,
   599  		"Root": "/Users/adg/go",
   600  		"GoFiles": [
   601  			"buffer.go",
   602  			"bytes.go",
   603  			"bytes_decl.go",
   604  			"reader.go"
   605  		],
   606  		...
   607  	}
   608  
   609  It's easy to write programs that consume this data.
   610  
   611  * go list's Package struct (1/3)
   612  
   613  The go tool's documented `Package` struct describes all the possible fields:
   614  
   615  	type Package struct {
   616  		Dir           string // directory containing package sources
   617  		ImportPath    string // import path of package in dir
   618  		ImportComment string // path in import comment on package statement
   619  		Name          string // package name
   620  		Doc           string // package documentation string
   621  		Target        string // install path
   622  		Shlib         string // the shared library that contains this package (only set when -linkshared)
   623  		Goroot        bool   // is this package in the Go root?
   624  		Standard      bool   // is this package part of the standard Go library?
   625  		Stale         bool   // would 'go install' do anything for this package?
   626  		Root          string // Go root or Go path dir containing this package
   627  
   628  		// (more fields on next slide)
   629  	}
   630  
   631  * go list's Package struct (2/3)
   632  
   633  	type Package struct {
   634  		// (more fields on previous slide)
   635  
   636  		// Source files
   637  		GoFiles        []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
   638  		CgoFiles       []string // .go sources files that import "C"
   639  		IgnoredGoFiles []string // .go sources ignored due to build constraints
   640  		CFiles         []string // .c source files
   641  		CXXFiles       []string // .cc, .cxx and .cpp source files
   642  		MFiles         []string // .m source files
   643  		HFiles         []string // .h, .hh, .hpp and .hxx source files
   644  		SFiles         []string // .s source files
   645  		SwigFiles      []string // .swig files
   646  		SwigCXXFiles   []string // .swigcxx files
   647  		SysoFiles      []string // .syso object files to add to archive
   648  
   649  		// (more fields on next slide)
   650  	}
   651  
   652  * go list's Package struct (3/3)
   653  
   654  	type Package struct {
   655  		// (more fields on previous slide)
   656  
   657  		// Cgo directives
   658  		CgoCFLAGS    []string // cgo: flags for C compiler
   659  		CgoCPPFLAGS  []string // cgo: flags for C preprocessor
   660  		CgoCXXFLAGS  []string // cgo: flags for C++ compiler
   661  		CgoLDFLAGS   []string // cgo: flags for linker
   662  		CgoPkgConfig []string // cgo: pkg-config names
   663  
   664  		// Dependency information
   665  		Imports []string // import paths used by this package
   666  		Deps    []string // all (recursively) imported dependencies
   667  
   668  		// Error information
   669  		Incomplete bool            // this package or a dependency has an error
   670  		Error      *PackageError   // error loading package
   671  		DepsErrors []*PackageError // errors loading dependencies
   672  
   673  		TestGoFiles  []string // _test.go files in package
   674  		TestImports  []string // imports from TestGoFiles
   675  		XTestGoFiles []string // _test.go files outside package
   676  		XTestImports []string // imports from XTestGoFiles
   677  	}
   678  
   679  * go list -f (1/2)
   680  
   681  That's a ton of information, so what can we do with it?
   682  
   683  The `-f` flag lets you use Go's `text/template` package to format the ouput.
   684  
   685  Show package doc strings:
   686  
   687  	$ go list -f '{{.Doc}}' golang.org/x/oauth2/...
   688  	Package oauth2 provides support for making OAuth2 authorized and authenticated HTTP requests.
   689  	Package jwt implements the OAuth 2.0 JSON Web Token flow, commonly known as "two-legged OAuth 2.0".
   690  	...
   691  
   692  Show the Go files in a package:
   693  
   694  	$ go list -f '{{.GoFiles}}' bytes
   695  	[buffer.go bytes.go bytes_decl.go reader.go]
   696  
   697  Make the output cleaner with a `join`:
   698  
   699  	$ go list -f '{{join .GoFiles " "}}' bytes
   700  	buffer.go bytes.go bytes_decl.go reader.go 
   701  
   702  * go list -f (2/2)
   703  
   704  With template logic we can test packages for certain conditions.
   705  
   706  Find standard libraries that lack documentation:
   707  
   708  	$ go list -f '{{if not .Doc}}{{.ImportPath}}{{end}}' std
   709  	internal/format
   710  	internal/trace
   711  
   712  Find packages that depend (directly or indirectly) on a given package:
   713  
   714  	$ go list -f '{{range .Deps}}{{if eq . "golang.org/x/oauth2"}}{{$.ImportPath}}{{end}}{{end}}' all
   715  	golang.org/x/build/auth
   716  	golang.org/x/build/buildlet
   717  	golang.org/x/build/cmd/buildlet
   718  	...
   719  
   720  Find packages that are broken somehow (note `-e`):
   721  
   722  	$ go list -e -f '{{with .Error}}{{.}}{{end}}' all
   723  	package github.com/golang/oauth2: code in directory /Users/adg/src/github.com/golang/oauth2
   724  		expects import "golang.org/x/oauth2"
   725  	...
   726  
   727  * go list and the shell
   728  
   729  Things get interesting once we start using the shell.
   730  
   731  For instance, we can use `go` `list` output as input to the go tool.
   732  
   733  Test all packages except vendored packages:
   734  
   735  	$ go test $(go list ./... | grep -v '/vendor/')
   736  
   737  Test the dependencies of a specific package:
   738  
   739  	$ go test $(go list -f '{{join .Deps " "}}' golang.org/x/oauth2)
   740  
   741  The same, but don't test the standard library:
   742  
   743  	$ go test $(
   744  		go list -f '{{if (not .Goroot)}}{{.ImportPath}}{{end}}' $(
   745  			go list -f '{{join .Deps " "}}' golang.org/x/oauth2
   746  		)
   747  	)
   748  
   749  * go list printing line counts
   750  
   751  	for pkg in $(go list golang.org/x/oauth2/...); do
   752  		wc -l $(go list -f '{{range .GoFiles}}{{$.Dir}}/{{.}} {{end}}' $pkg) | \
   753  			tail -1 | awk '{ print $1 " '$pkg'" }'
   754  	done | sort -nr
   755  
   756  The output:
   757  
   758  	617 golang.org/x/oauth2/google
   759  	600 golang.org/x/oauth2
   760  	357 golang.org/x/oauth2/internal
   761  	160 golang.org/x/oauth2/jws
   762  	147 golang.org/x/oauth2/jwt
   763  	112 golang.org/x/oauth2/clientcredentials
   764  	22 golang.org/x/oauth2/paypal
   765  	16 golang.org/x/oauth2/vk
   766  	16 golang.org/x/oauth2/odnoklassniki
   767  	16 golang.org/x/oauth2/linkedin
   768  	16 golang.org/x/oauth2/github
   769  	16 golang.org/x/oauth2/facebook
   770  
   771  * go list generating dependency graphs
   772  
   773  	(   echo "digraph G {"
   774  		go list -f '{{range .Imports}}{{printf "\t%q -> %q;\n" $.ImportPath .}}{{end}}' \
   775  			$(go list -f '{{join .Deps " "}}' time) time
   776  		echo "}"
   777  	) | dot -Tsvg -o time-deps.svg
   778  
   779  .image tricks/time-deps.png 400 _
   780  
   781  
   782  * And there's more!
   783  
   784  There are many more fun corners of Go.
   785  Can you find them all? :-)
   786  
   787  Read the docs, explore, and have fun!