10 things you (probably) don't know about Go

Andrew Gerrand

Gopher

1. Anonymous structs

var config struct {
    APIKey      string
    OAuthConfig oauth.Config
}

config.APIKey = "BADC0C0A"
data := struct {
    Title string
    Users []*User
}{
    title,
    users,
}
err := tmpl.Execute(w, data)

(Cheaper and safer than using map[string]interface{}.)

2

1b. Anonymous structs

var indexRuneTests = []struct {
    s    string
    rune rune
    out  int
}{
    {"a A x", 'A', 2},
    {"some_text=some_value", '=', 9},
    {"☺a", 'a', 3},
    {"a☻☺b", '☺', 4},
}
var hits struct {
    sync.Mutex
    n int
}

hits.Lock()
hits.n++
hits.Unlock()
3

2. Nested structs

{"data": {"children": [
  {"data": {
    "title": "The Go homepage",
    "url": "https://go.dev/"
  }},
  ...
]}}
type Item struct {
    Title string
    URL   string
}

type Response struct {
    Data struct {
        Children []struct {
            Data Item
        }
    }
}
4

3. Command-line godoc

% godoc sync Mutex
PACKAGE

package sync
    import "sync"

TYPES

type Mutex struct {
    // contains filtered or unexported fields
}
    A Mutex is a mutual exclusion lock. Mutexes can be created as part of
    other structures; the zero value for a Mutex is an unlocked mutex.

func (m *Mutex) Lock()
    Lock locks m. If the lock is already in use, the calling goroutine
    blocks until the mutex is available.

func (m *Mutex) Unlock()
    Unlock unlocks m. It is a run-time error if m is not locked on entry to
    Unlock.

    A locked Mutex is not associated with a particular goroutine. It is
    allowed for one goroutine to lock a Mutex and then arrange for another
    goroutine to unlock it.
5

4. godoc -src

% godoc -src sync Mutex
// A Mutex is a mutual exclusion lock.
// Mutexes can be created as part of other structures;
// the zero value for a Mutex is an unlocked mutex.
type Mutex struct {
    state int32
    sema  uint32
}

Also shows unexported state! Great for digging around.

6

5. go get supports custom domains

Yep:

go get camlistore.org/pkg/netutil

See `go help importpath` for the details.

7

6. Mock out the file system

Got a package that works with the file system, but don't want your tests to actually use the disk?

var fs fileSystem = osFS{}

type fileSystem interface {
    Open(name string) (file, error)
    Stat(name string) (os.FileInfo, error)
}

type file interface {
    io.Closer
    io.Reader
    io.ReaderAt
    io.Seeker
    Stat() (os.FileInfo, error)
}

// osFS implements fileSystem using the local disk.
type osFS struct{}

func (osFS) Open(name string) (file, error)        { return os.Open(name) }
func (osFS) Stat(name string) (os.FileInfo, error) { return os.Stat(name) }

Implement your own fake fileSystem in and put it in fs while testing.

8

7. Method expressions

type T struct {}
func (T) Foo(s string) { println(s) }

var fn func(T, string) = T.Foo

Real example from os/exec:

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)
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)
}
9

8. Send and receive on the same channel

package main

import "fmt"

var battle = make(chan string)

func warrior(name string, done chan struct{}) {
    select {
    case opponent := <-battle:
        fmt.Printf("%s beat %s\n", name, opponent)
    case battle <- name:
        // I lost :-(
    }
    done <- struct{}{}
}

func main() {
    done := make(chan struct{})
    langs := []string{"Go", "C", "C++", "Java", "Perl", "Python"}
    for _, l := range langs { go warrior(l, done) }
    for _ = range langs { <-done }
}
10

9. Using close to broadcast

// +build ignore,OMIT

package main

import (
	"fmt"
	"time"
	"math/rand"
)

func waiter(i int, block, done chan struct{}) {
    time.Sleep(time.Duration(rand.Intn(3000)) * time.Millisecond)
    fmt.Println(i, "waiting...")
    <-block
    fmt.Println(i, "done!")
    done <- struct{}{}
}

func main() {
    block, done := make(chan struct{}), make(chan struct{})
    for i := 0; i < 4; i++ {
        go waiter(i, block, done)
    }
    time.Sleep(5 * time.Second)
    close(block)
    for i := 0; i < 4; i++ {
        <-done
    }
}
11

9b. Using close to broadcast

// +build ignore,OMIT

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func worker(i int, ch chan Work, quit chan struct{}) {
    var quitting bool
    for {
        select {
        case w := <-ch:
            if quitting {
                w.Refuse(); fmt.Println("worker", i, "refused", w)
                break
            }
            w.Do(); fmt.Println("worker", i, "processed", w)
        case <-quit:
            fmt.Println("worker", i, "quitting")
            quitting = true
        }
    }
}

func main() {
    ch, quit := make(chan Work), make(chan struct{})
    go makeWork(ch)
    for i := 0; i < 4; i++ { go worker(i, ch, quit) }
    time.Sleep(5 * time.Second)
    close(quit)
    time.Sleep(2 * time.Second)
}

type Work string
func (w Work) Do() {
	time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
}
func (w Work) Refuse() {
	time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
}

func makeWork(ch chan Work) {
	for i := 0; ; i++ {
		ch <- Work(fmt.Sprintf("job %x", i))
	}
}
12

10. Nil channel in select

// +build ignore,OMIT

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func worker(i int, ch chan Work, quit chan struct{}) {
    for {
        select {
        case w := <-ch:
            if quit == nil {
                w.Refuse(); fmt.Println("worker", i, "refused", w)
                break
            }
            w.Do(); fmt.Println("worker", i, "processed", w)
        case <-quit:
            fmt.Println("worker", i, "quitting")
            quit = nil
        }
    }
}

func main() {
    ch, quit := make(chan Work), make(chan struct{})
    go makeWork(ch)
    for i := 0; i < 4; i++ { go worker(i, ch, quit) }
    time.Sleep(5 * time.Second)
    close(quit)
    time.Sleep(2 * time.Second)
}

type Work string
func (w Work) Do() {
	time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
}
func (w Work) Refuse() {
	time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
}

func makeWork(ch chan Work) {
	for i := 0; ; i++ {
		ch <- Work(fmt.Sprintf("job %x", i))
	}
}
13

11. The gopher's name

14

Thank you

Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)