gothamgo 2018 workshop afternoon

gothamgo 2018 workshop afternoon

Testing in Go


Initializing maps

  beatles := map[string]string{
    "John":   "guitar",
    "Paul":   "bass",
    "George": "guitar",
    "Ringo":  "drums",

Map keys

type simple struct {
  ID int

type complex struct {
  f func(id int) simple

func main() {
  m := map[simple]string{}


  // invalid map key type complex
  m1 := map[complex]string{}


for key, value := range beatles {
  fmt.Printf("%s plays %s\n", key, value)

Validating if an accessed value exists

  // Look up the key `Bob` and detect that it wasn't found by printing `not found`
  _, ok := beatles["Bob"]

  if !ok {
    fmt.Println("not found")

  // or more simply
  if _, ok := beatles["Bob"]; !ok {
    fmt.Println("not found")

Thread Safety


pass by value vs pass by reference

Pass by value

type Beatle struct {
  Name string

func main() {
  b := Beatle{Name: "Ringo"}
  fmt.Println(b.Name) // Ringo

func changeBeatleName(b Beatle) {
  b.Name = "George"
  fmt.Println(b.Name) // George

To pass by reference

To indicate we are expecting a pointer, we use the * modifier:

func changeBeatleName(b *Beatle) {}

To get the address of a value we use the & modifier:


And we can store pointers too:

b := &Beatle{}
func main() {
  x := "George"

func printValue(s *string) {
  // print the pointer value

  // print the value of a pointer to this pointer

  // print the string value

  // change the string value
  *s = "Susan"

// Produces
// 0xc42000e1e0
// George
// Susan

Pointer type checking

go is so strongly typed that you can’t do this

type Beatle struct {
    Name string

type NonBeatle struct {
    Name string

func main() {
    b := &Beatle{Name: "Ringo"}
    n := &NonBeatle{Name: "Susan"}


func changeUserName(b *Beatle) {
    b.Name = "George"
    fmt.Println(b.Name) // George

will result in

cannot use n (type *NonBeatle) as type *Beatle in argument to changeUserName

Security vs. Performance

By default go passes by value for security, but it can be more performant to pass a pointer to a large file by pointer (reference)


type Entertainer interface {

// to invoke a passed concrete struct that implemented the interface
func entertain(e Entertainer) {

Inteface implementation

type scribe struct {
  data []byte

// note the pointer receiver `s *scribe` is because you need to mutate the byte field, you have to pass a reference
// if you change it to a value accessor `s scribe`, value accessor will not allow a mutate
// the compiler will allow both but it's more maintainability
func (s *scribe) Write(p []byte) (int, error) { = p
  return len(p), nil

Pointer receiver vs. value receivers

type User struct {
  First string
  Last  string

func (u *User) String() string {
  return fmt.Sprintf("%s %s", u.First, u.Last)

func pretty(v fmt.Stringer) {

func main() {
  u := User{First: "Rob", Last: "Pike"}

/* produces
   cannot use u (type User) as type fmt.Stringer in argument to pretty:
   User does not implement fmt.Stringer (String method has pointer receiver)

 A. use
 func (u User) String() string {
  return fmt.Sprintf("%s %s", u.First, u.Last)

 or B. pass the reference

Stylist notes

// good
type Writer interface {
  Write(p []byte) (int, error)

// bad
type Emailer interface {
  Email string

Empty interface

// generic empty interface:

// a named empty interface:
type foo interface{}


No try/catch

f, err := os.Open("/not/a/real/path")
if err != nil { // note you should explicitly check for nil, `if !err` only works for bool
  // alert
  return or break

// f is the file handle

creating errors

Defining errors in the standard library

// in the `errors` package
errors.New("a message")

// in the `fmt` package
fmt.Errorf("a %s message", "formatted")

Defining custom errors

type error interface {
  Error() string

Recovering from panics

func main() {
  defer func() {
    if err := recover(); err != nil {
  a := []string{}
  a[42] = "Bring a towel"

Sentinenal errors

Alternate error package with stack and wrap

Best practices


go routine

func sayHello() { fmt.Println(“hello”) }

This won't print anything, because main exited because the goroutine finished. Instead just wait.

### waitgroup

* Add function
Add adds delta, which may be negative, to the WaitGroup counter. If the counter becomes zero, all goroutines blocked on Wait are released. If the counter goes negative, Add panics.

func main() {
  wg := &sync.WaitGroup{}
  go sayHello(wg)

func sayHello(wg *sync.WaitGroup) {
  defer wg.Done()

anonymous functions and closure

Instead of explicitly defining sayHello, you can use an anonymous function

for i := 0; i < 5; i++ {
  go func(i int) {
    defer wg.Done()
    fmt.Printf("starting %d...\n", i)
    time.Sleep(1 * time.Second)
    fmt.Printf("ending %d...\n", i)

Note, you must pass in the i variable. If you don’t, the scope of i would at the outer scope. Since the goroutines are being added to the execution stack, but not run, by the time they run, and access that memory space, i has already been changed.

Sending i in as an argument creates a new scope and copies the value to a new memory location to preserve the proper value.


A mutex allows you to synchronize goroutines for safe access to the same shared memory. There are two types of mutexes

  1. full lock
  2. read/write lock
func main() {
  var mu sync.Mutex
  var counter int

  set := func(i int) {
    defer mu.Unlock()
    counter = i

  get := func() int {
    defer mu.Unlock() // Note, defer will execute at the exit of a function, not at the exit of a code {} block
    return counter

  go func() {
    for {
      time.Sleep(500 * time.Millisecond)
      i := get()
      fmt.Printf("counter: %d\n", i)

  var wg sync.WaitGroup

  for i := 0; i <= 10; i++ {
    go func(i int) {
    time.Sleep(750 * time.Millisecond)



Channels are a typed conduit through which you can send and receive values.

initialize a channel

message := make(chan string)

Send and receive

ch <- // data is going into the channel
<- ch // data is coming out of the channel

Buffered channels

By default channels are unbuffered – sends on a channel will block until there is something ready to receive from it. You can create a buffered channel - which allows you to control how many items can sit in a channel unread.

  // Adding a second argument to the make function creates a buffered channel
  messages := make(chan string, 2)

Channels are not message queues. Only one channel will receive a message sent down a channel. If multiple goroutines are listening to a channel, only one will receive the message.

Closing Channels

You can signal that there are no more values in a channel by closing it.

func echo(s string, c chan string) {
  for i := 0; i < cap(c); i++ {
    // write the string down the channel
    c <- s

func main() {
  // make a channel with a capacity of 10
  c := make(chan string, 10)

  // launch the goroutine
  go echo("hello!", c)

  for s := range c {

books and channel