Wednesday, 31 October 2018

Go Puzzles

Here are a few puzzles intended to help you round out your knowledge of the Go programming language.

Background

In 1983 I had read my copy of K&R from cover to cover (twice I think) and believed I knew everything there was to know about C. I thought K&R (The C Programming Language by Kernighan and Ritchie) was the only book on C that existed but one day I came across another - the C Puzzle Book by Alan Feuer. I bought it and soon discovered that I knew very little about C - such as the myriad possibilities for undefined behavior.

Luckily Go has greatly limited the prospects of undefined behavior. However, I still think there is room for a selection of puzzles to test your knowledge of the language. Most of puzzles below are the result of discovering something I didn't know in my reading of the excellent "Go Programming Language" by Donovan and Kernighan.

The questions are in no particular order but I tried to start with easy ones. Don't worry if you get most of them wrong as some are pretty obscure - I'd get a few wrong myself even though I wrote them all!

The Questions

For brevity most of the puzzles only provide a snippet of Go code. In which case, you can assume that the code is wrapped in a main() function with all relevant imports added.
  1. m := map[string]int{"": 0}
    mcopy := m
    mcopy["1"] = 1
    mcopy["0"] = 0
    mcopy[""] = 1

    print(len(m))

    What does this print?
    a.  1
    b.  2
    c.  3
    d.  4
     
  2. s := []int{1, 2, 3, 4}
    copy(s, []int{42})
    fmt.Println(s)

    What does this print?
    a.  [42]
    b.  [42 1 2 3 4]
    c.  [1 2 3 4 42]
    d.  [42 2 3 4]
     
  3. defer func() {
      defer func() {
        fmt.Print(recover())
      }()
      fmt.Print(recover())
      panic("DEF")
    }()
    panic("ABC")

    What does this print?
    a.  ABC
    b.  DEF
    c.  ABCDEF
    d.  <nil>
     
  4. c := 'c'
    print(strings.IndexFunc("abcdefg", func(r rune) bool {
      return r == c
    }))

    What does this print?
    a.  -1
    b.  2
    c.  3
    d.  compile error: undefined: c
     
  5. s := "世界"
    fmt.Println(len(s), len([]byte(s)))

    What does this print?
    a.  2 2
    b.  6 2
    c.  6 6
    d.  2 6
     
  6. func init() {
      i = make(chan int, 2)
      i <- 1
    }

    var i chan int

    func init() {
      i <- 2
    }

    func main() {
      fmt.Println(<-i)
    }

    What does this print?
    a.  1
    b.  2
    c.  prints nothing and never terminates
    d.  compilation error: init redeclared in this block
     
  7. const (
     a = iota
     b
     c
     d
    )
    fmt.Println(a+b+c+d)

    What does this print?
    a.  0
    b.  4
    c.  6
    d.  10
     
  8. a := [...]int{41, 42, 43, 44, 45}
    s := a[1:2]
    copy(s, []int{1, 2, 3})
    fmt.Println(a[2])

    What does this print?
    a.  2
    b.  3
    c.  42
    d.  43
     
  9. m := map[int]int{1: 1, 2: 2}
    for k := range m {
      if k == 1 {
        delete(m, k)
      }
      fmt.Print(k, len(m), " ")
    }

    What does this print?
    a.  1 1
    b.  1 1 2 1
    c.  2 2 1 1
    d.  either b or c
     
  10. a := []int{0, 1, 2, 3, 4}
    b := a[0:2:4]
    fmt.Println(cap(b) - len(b))

    What does this print?
    a.  0
    b.  2
    c.  3
    d.  4
     
  11. var wg sync.WaitGroup
    wg.Add(1)
    go func(wg sync.WaitGroup) {
      wg.Wait()
      print("A")
    }(wg)
    wg.Done()
    print("B")

    What does this print?
    a.  AB
    b.  B
    c.  BA
    d.  any one of the above
     
  12. i, f := 200, 2e2
    x, y, z := 200+2e2, 200+f, i+2e2
    fmt.Printf("%T %T %T", x, y, z)

    What does this print?
    a.  int float64 int
    b.  float64 float64 int
    c.  float64 float64 float64
    d.  None of the above
     
  13. s := []int{1, 2, 3}[1:2]
    t := append(s, 4, 5, 6)
    fmt.Println(len(s), cap(t))

    What does this print?
    a.  1 4
    b.  2 5
    c.  4 4
    d.  4 6
     
  14. const idx = 2
    a := [...]int{idx: 1, idx+1: 2}
    fmt.Println(a[3])

    What does this print?
    a.  0
    b.  1
    c.  2
    d.  compilation error: invalid array index 3
     
  15. a := 1
    var b interface{} = a
    a = 2
    fmt.Println(b)

    What does this print?
    a.  1
    b.  2
    c.  (interface {})(1)
    d.  (interface {})(2)
     
  16. const (
       a = 0
       b
       c = iota
       d
    )
    fmt.Println(a+b+c+d)

    What does this print?
    a.  1
    b.  2
    c.  5
    d.  6
     
  17. a := [...]int{1, 2}
    _ = append(a[:1], 3)
    _ = append(a[:1], 4, 5)
    fmt.Println(a[1])

    What does this print?
    a.  0
    b.  2
    c.  3
    d.  4
     
  18. s := bytes.NewBufferString("string")
    a := s.String()
    b := s.Bytes()
    m := make(map[interface{}]int)
    m[a] = 1
    m[b] = 2
    log.Println(m[a])

    What does this print?
    a.  1
    b.  2
    c.  compile error: invalid map key
    d.  panics at run-time
     
  19. m := make(map[int]*struct{ i int })
    m[3] = &struct{ i int }{1}
    v := m[3]
    v.i++
    fmt.Println(m[3].i)

    What does this print?
    a.  0
    b.  1
    c.  2
    d.  3
     
  20. a := time.Now().Year
    b, _ := strconv.Atoi(time.Now().Format("06"))
    fmt.Println(b - a())

    What does this print?
    a.  6
    b.  0
    c.  -2000
    d.  18 (current year this century)
     
  21. i := 2
    switch i {
    case 2:
       if i % 2 == 0 {
          fallthrough
       }
       i = 6
    case 3:
       i++
       fallthrough
    default:
       i++
    }
    print(i)

    What does this print?
    a.  4
    b.  5
    c.  6
    d.  compile error: fallthrough statement out of place
     
  22. s := []int{10, 12, 14}
    for _, v := range s {
       v++
    }
    fmt.Println(s[1])

    What does this print?
    a.  11
    b.  12
    c.  13
    d.  14
     
  23. s := []int{1, 2, 3, 4}
    t := make([]int, 2, 3)
    copy(t, s)
    fmt.Println(t)

    What does this print?
    a.  [1 2]
    b.  [1 2 3]
    c.  [1 2 3 4]
    d.  [0 0 3 4]
  24. a := struct{}{}
    b := struct{}{}
    fmt.Printf("%t", &a == &b)

    What does this print?
    a.  false
    b.  true
    c.  bool
    d.  the exact behaviour of this code is undefined
     
  25. var a, b interface{}
    a = "str" + "ing"
    b = "string"
    m := make(map[interface{}]int)
    m[a] = 1
    m[b] = 2
    print(m[a])

    What does this print?
    a.  0
    b.  1
    c.  2
    d.  compile error: invalid map key
     
  26. var t interface{} = 1
    switch t := t.(type) {
    case int, uint:
       fmt.Println(reflect.TypeOf(&t))
    default:
       print("default")
    }

    What does this print?
    a.  *int
    b.  *uint
    c.  default
    d.  *interface{}
     
  27. fmt.Printf("%q\n", string(65))

    What does this print?
    a.  "65"
    b.  "A"
    c.  "\x41"
    d.  "�"
     
  28. const i1, i2, i3 = 2, 3, 2
    a := [...]int{i1: i1, i2: i2, i3: i3}
    print(len(a))

    What does this print?
    a.  2
    b.  3
    c.  4
    d.  compile error: duplicate index in array literal: 2
     
  29. type intA int
    type intB = int
    var a intA = 1
    var b intB = 2
    print(int(a) | b)

    What does this print?
    a.  0
    b.  1
    c.  3
    d.  compile error: invalid operation: int(a) | b (mismatched types int and intB)
     
  30. ctx, cancel := context.WithCancel(context.Background())
    cancel()
    fmt.Println(ctx.Err())

    What does this print?
    a.  <nil>
    b.  deadline exceeded
    c.  context canceled
    d.  does not print anything but causes a panic at run-time
     
  31. const (
       a, b = iota, iota
       c, d = iota+1, iota+2
    )
    print(a+b+c+d)

    What does this print?
    a.  5
    b.  6
    c.  7
    d.  9
     
  32. var a, b interface{}
    a = "main"
    b = main
    fmt.Printf("%t\n", a == b)

    What does this print?
    a.  false
    b.  true
    c.  compile error: invalid interface type
    d.  panics at run-time
     
  33. fmt.Printf("%q", "A\xbc")

    What does this print?
    a.  "A\xbc"
    b.  A
    c.  A�
    d.  "Abc"
     

Tuesday, 2 October 2018

Go Error Handling – Using Closures

The Go 2 Error handling draft design was released recently (see Error Handling Overview). I took great interest in it as it is similar to my own proposal made last year (see Improving Go Error Handling) but goes a step further by adding "handlers".

I like the reasoning behind handlers but I believe something better is needed. Handlers have been discussed at length in the feedback page (see wiki feedback page) so I won't add to that noise. [If you have not read any of the other feedback then basically people find handlers confusing - both conceptually and in use, especially if chained.]

Proposal – using Closures 

Instead of little bits of code in handle statements why not simply use the existing Go facility of closures. A closure could be provided for error-handling of a specific expression by putting its name in brackets after the checked keyword.  All such error-handling closures would take one (error) parameter and return an error value.

As in the original draft design, a "default" handler is provided. This is used if the check keyword does not specify a closure.  The "default" handler would be simply implemented as:

func defaultHandler(err error) error { return err }

The advantages to this are:
  • it is clearer about where an error for any specific expression is handled 
  • handler "chaining" is still possible  (see example below)
  • closures make chaining more obvious
  • closures provide even more flexibility than handlers/chaining
  • error-handling code can be easily separated so as not to obscure the code 
  • but it is still easy to trace how the error is handled if necessary
  • error handling code can be moved outside the function and even re-used 
Here is the CopyFile() example from the Error Handling Overview done in this way:

func CopyFile(src, dst string) error {
  eMess := func(err error) error {
    return fmt.Errorf("copy %s %s: %v", src, dst, err)
  }

  r := check(eMess) os.Open(src)
  defer r.Close()
  w := check(eMess) os.Create(dst)

  eClose := func(err error) err {
    w.Close()
    os.Remove(dst)
    return eMess(err)
  }

  check(eClose) io.Copy(w, r)
  check(eClose) w.Close()
  return nil
}

Further Proposal – Check Statement 

 A further idea is to also allow check to work as a statement. Enclosing statements in a check statement would allow the same check operation to be applied to any contained function calls that returned an error as the last returned value.  Using the same example this could be used this way:

func CopyFile(src, dst string) error {
  eMess := func(err error) error {
    return fmt.Errorf("copy %s %s: %v", src, dst, err)
  }
  check(eMess) {
    r := os.Open(src)
    defer r.Close()
    w := os.Create(dst)
  }

  eClose := func(err error) err {
    w.Close()
    os.Remove(dst)
    return eMess(err)
  }
  check(eClose) {
    io.Copy(w, r)
    w.Close()
  }
  return nil
}

Separating Error Handling Code 

 Note that the closure used with the check statement could be an expression evaluating to a function (just as happens with the defer statement).  That is, it could be a function call returning a closure.  This allows all the error-handling code to be hived off to another function where it is less distracting.

func CopyFile(src, dst string) error {
  var w *File
  check(genErr(src, dst, w)) {
    r := os.Open(src)
    defer r.Close()
    w = os.Create(dst)
    io.Copy(w, r)
    w.Close()
    w = nil
  }
  return nil
}

func genErr(src, dst string, w *os.File) func(err error ) error {
  return func(err error ) error {
    if w != nil {
      // output file was opened so close and remove it
      w.Close()
      os.Remove(dst)
    }
    return fmt.Errorf("copy %s %s: %v", src, dst, err)
  }
}

Summary

Using closures instead of adding the new feature of handlers fits better with how Go currently works and makes use of the Go's wonderful feature of closures.

I'd like to thank Rob Pike for speaking at the Sydney Go Meetup last Thursday. His talk inspired me to think further about Go error-handling and write this post. Go is by far the least annoying language I have ever used and hopefully the addition of an improved error handling facility will make it even less so.