5 February 2024

Go Quiz

Do you know Go?

  1. T/F? In Go, semicolons are required to terminate statements.
    Answer True, but it's usually the compiler that inserts them on behalf of the programmer. Go Spec: Semicolons
  2. T/F? You can define lexically scoped blocks anywhere in a function.
    Answer

    True: Go Spec: Blocks (which means you can do stuff like this.

  3. T/F? A deferred function's arguments are not evaluated until the deferred statement is executed.
    Answer

    False: The Go Blog: Defer, Panic, and Recover

    A deferred function's arguments are evaluated when the defer statement is evaluated.
  4. T/F? In a panicking goroutine recover() will return what panic() received & resume execution.
    Answer

    True: The Go Blog: Defer, Panic, and Recover

    Recover is a built-in function that regains control of a panicking goroutine...During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.
  5. T/F? The recover function is only useful inside deferred functions.
    Answer

    True: The Go Blog: Defer, Panic, and Recover

    Recover is only useful inside deferred functions.
  6. T/F? Deferred function calls are executed in First-In-First-Out order.
    Answer

    False: The Go Blog: Defer, Panic, and Recover

    Deferred function calls are executed in Last In First Out order after the surrounding function returns.
  7. T/F? Deferred functions may read but not assign to the returning function's named return values.
    Answer

    False: The Go Blog: Defer, Panic, and Recover

    Deferred functions may read and assign to the returning function’s named return values.
  8. T/F? Excluding cases such as panics, a go program exits when all goroutines have finished.
    Answer

    False: The Go Spec: Program Execution

    Program execution begins by initializing the program and then invoking the function main in package main. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.
  9. T/F? Naked returns (like naked mole rats) are visually unappealing, best left out of sight.
    Answer

    TRUE

  10. Given type Point struct { X, Y int } why would this code make any self-respecting gopher cringe? p := Point{1, 2}
    Answer

    The assignment statement utilizes a composite literal (see the Go Spec: Composite Literals. When a struct type is initialized using a composite literal:

    An element list that does not contain any keys must list an element for each struct field in the order in which the fields are declared.

    The above is a very rigid requirement. Always specifying keys (field names) in struct literals is a much more flexible way to code and allows for the addition and re-ordering of fields without breaking any calling code.

  11. Complete the following two Go Proverbs: 'Clear is better than ______. __________ is never clear.'
    Answer

    Clear is better than clever. Reflection is never clear.

  12. Jeopardy Clue: "The utility whose style is no one's favorite, but is still everyone's favorite."
    Answer

    What is go fmt? (see the Go Proverbs)

  13. What is the difference between concurrency and parallelism, and why does it matter?
    Answer

    Concurrent execution refers to the capability of the runtime to multi-task when presented with, for instance, blocking I/O. Parallelism refers to executing multiple tasks at the same time (using multiple processors/threads). A concurrent program written in Go can be executed in parallel simply by ensuring that GOMAXPROCS is greater than 1 (the default value since Go 1.5 has been the number of cores available).

  14. The strings.NewReplacer() function panics if an odd number of arguments are passed. Is this a good approach?
    Answer

    It certainly seems odd for a standard library function such as this to panic if passed an odd number of arguments. Perhaps it would be better to have defined the function with an error return value:

    def NewReplacer(args ...string) (*Replacer, error) {...}

    Because of the Go 1.0 compatibility promise, the signature for this function won't be changing anytime soon, but we could create a custom 'builder' to smooth this out, something with a Register(search, replace string) method.

  15. What fmt 'verb' produces the same output as fmt.Sprintf("%s", reflect.TypeOf(a)) but without any call to reflect.TypeOf()?
    Answer

    fmt.Sprintf("%T", a)

  16. T/F? Spaces are always added between operands passed to fmt.Println().
    Answer

    True

  17. T/F? Spaces are always added between operands passed to fmt.Print().
    Answer

    False (only when neither is a string)

  18. Simplify by removing a redundant element: csv.NewReader(bufio.NewReader(file))
    Answer

    Since the csv.NewReader function wraps the provided reader in bufio.NewReader for the caller, it's sufficient to simply use csv.NewReader(file).

  19. Simplify by replacing with another 'strings' function: strings.Split(s, "\t\n\v\f\r ")
    Answer

    strings.Fields(s)

  20. Simplify by replacing with another 'strings' function: strings.Replace(s, "a", "b", -1)
    Answer

    strings.ReplaceAll(s, "a", "b")

  21. T/F? a := new(Thing) is equivalent to a := Thing{}
    Answer

    False! The new function returns a pointer.

  22. T/F? var a any = new(Thing) is equivalent to var a any = reflect.New(reflect.TypeOf(Thing{})).Interface()
    Answer

    True! The reflect.New function is analogous to the built-in new function. They both return pointers to a newly allocated zero-value of the provided type.

  23. T/F? t1 := T{}; t2 := reflect.ValueOf(t1).Interface(); fmt.Print(t1 == t2)
    Answer

    True! See "The Laws of Reflection".

  24. How many bits are in every int?
    Answer

    The answer is dependent upon processor architecture. Most likely your int values have 64 bits, unless the architecture of your processor is 32-bit.

  25. In Go, while is spelled ___.
    Answer

    for

    There are 3 forms of the for loop in Go. The one called "For statements with single condition" operates the same as while loops common in other languages.

  26. T/F? After being compiled/parsed, regular expression patterns should only be used once.
    Answer

    False! The point of compiling a regular expression is so you can then reuse it many times. In my own benchmarks, compiling a simple regex takes ~800ns while matching against a string with the compiled regex only takes ~80ns (that's a 10x difference). So, for simple regular expressions and small strings, it's very advantageous to pre-compile the regular expression pattern once and then reference it as needed later. Moral of the story: Don't call regexp.Compile(...) every time you want to perform a match.

    Try writing your own benchmarks to see how the complexity of a regular expression pattern and the length of the text against which you match affect runtime. (The results may surprise you!)

  27. What does io.EOF stand for and in what scenarios might that be something of a misnomer?
    Answer

    End of file. It is a slight misnomer in the context of any io.Reader whose underlying source is NOT an instance of *os.File. Perhaps this is why, in the official io package documentation, the acronym is never explicitly decoded.

  28. T/F? any is an alias for interface{} and is equivalent to interface{} in all ways.
    Answer

    True

  29. T/F? make(map[k]v) is the same as map[k]v{}
    Answer

    True

  30. T/F? Attempting to retrieve a map value will panic if the key is not present.
    Answer

    False! The zero value will be returned (without any panic).

  31. T/F? Attempting to retrieve a value from a nil map will cause a panic.
    Answer

    False! The zero value will be returned.

  32. T/F? Maps are safe for concurrent use across any number of goroutines.
    Answer

    False! Concurrent map access must be protected by a mutex. Alternatively, you could consider using sync.Map.

  33. T/F? Struct types may be used as map keys.
    Answer

    True! This is a handy way to index values by a combination of key properties.

  34. T/F? When iterating over a map with a range loop, the iteration order is not specified.
    Answer

    True! It is also not guaranteed to be the same from one iteration to the next.

  35. A sync.WaitGroup can be compared to a thread-safe (A) Queue, (B) Counter, (C) Stack, or (D) Mutex.
    Answer

    B. The sync.WaitGroup is effectively a fancy (i.e. 'thread-safe') Counter with some pretty specific rules about how it should be used.

  36. T/F? Calling sync.WaitGroup.Add() is ok at any point during the life cycle of the WaitGroup.
    Answer

    False! Add should be called from the 'main' goroutine and should not be called while a call to Wait() is blocked.

  37. T/F? The sync.WaitGroup.Done method can be called many times from each awaited goroutine.
    Answer

    True, but only if you like code that panics. In practice, Done should only be called once from each awaited goroutine, usually as a deferred call.

  38. T/F? The sync.WaitGroup.Wait method blocks until the counter is zero or up to 10 minutes.
    Answer

    False, there is no timeout. It blocks until all awaited goroutines finish. End of story.

  39. T/F? A sync.WaitGoup should only be reused once all Wait calls unblock.
    Answer

    True! Don't get fancy with a WaitGroup. Use it to wait for a batch of goroutines. Only after Wait has unblocked may you use the same instance to wait for another batch of goroutines.

  40. T/F? It is possible to recover from a panicking goroutine in the goroutine that launched it.
    Answer

    False! You must call recover somewhere higher in the call stack of panicking goroutine, otherwise the program will crash.

  41. T/F? A Git repository may contain a single Go module definition.
    Answer

    False! A single Git repository may contain multiple Go module definitions.

  42. T/F? A module version number like 'v0.*.*' makes no stability or backward compatibility guarantees.
    Answer

    True! It is only once a module graduates to 'v1.*.*' that module developers must be conscious of backward compatibility.

  43. T/F? To publish a breaking change to a 'v1' module, simply tag with 'v2.0.0' and push it.
    Answer

    False! Once a module reaches 'v2.*.*' it must contain '/v2' in its module declaration and package import paths. Modules at 'v0.*.*' or 'v1.*.*' are not subject to any such requirement.

  44. Fill in the Go proverb: "A little _______ is better than a little dependency."
    Answer

    "copying"

  45. Fill in the Go proverb: "Errors are ______."
    Answer

    "values"

  46. Fill in the Go proverb: "The bigger the interface, the ______ the abstraction."
    Answer

    "weaker"

  47. T/F? The following is an untyped string const: const a = "b"
    Answer

    True! See Constants on the Go blog.

  48. T/F? generic funcs work with arguments of various types based on type parameters and constraints.
    Answer

    True! Generics in Go (aka "type parameters") enable writing reusable code that can work with various data types. Common example: a function that can operate on a slice of any item type.

  49. T/F? Go's implementation of generics provides dynamic typing, i.e. types are determined at runtime.
    Answer

    False. Go's generics are statically typed, meaning type information is determined at compile time.

  50. T/F? Generics in Go can only be used with built-in types like slices and maps.
    Answer

    False. Generics in Go can be used with user-defined types as well, providing flexibility in creating generic algorithms and data structures.

  51. T/F? Go's generics feature was introduced in Go 1.18 as an experimental feature.
    Answer

    False. Go 1.18 introduced generics as an official feature, but a few packages were released as 'experimental' on the golang.org/x/exp subrepository (constraints/slices/maps) to allow developers to use and provide feedback on real-world scenarios. Much of the code defined in those experimental packages has been incorporated into the standard library since then.

  52. This question can be found at the Go Playground, but here's the relevant snippet of code:

    Reveal code snippet
    date_2023_01_30 := time.Date(2023, time.January, 30, 0, 0, 0, 0, time.UTC)
    one_month_later := date_2023_01_30.AddDate(0, 1, 0)
    date_2023_02_28 := time.Date(2023, time.February, 28, 0, 0, 0, 0, time.UTC)
    fmt.Println("T/F?", one_month_later == date_2023_02_28)
    Answer

    False! The one_month_later value is equal to "2023-03-02". From the docs:

    AddDate normalizes its result in the same way that Date does, so, for example, adding one month to October 31 yields December 1, the normalized form for November 31.
  53. T/F? make(chan int) is equivalent to make(chan int, 1).
    Answer

    False! make(chan int) is equivalent to make(chan int, 0).

  54. T/F? A send to a nil channel panics.
    Answer

    False! A send to a nil channel blocks forever. See Channel Axioms, by Dave Cheney

  55. T/F? A receive from a nil channel panics.
    Answer

    False! A receive from a nil channel blocks forever. See Channel Axioms, by Dave Cheney

  56. T/F? A send to a closed channel panics.
    Answer

    True! See Channel Axioms, by Dave Cheney

  57. T/F? A receive from a closed channel panics.
    Answer

    False! A receive from a closed channel returns the zero value immediately. See Channel Axioms, by Dave Cheney

  58. T/F? A closed channel never blocks.
    Answer

    True! See Curious Channels, by Dave Cheney

  59. T/F? A nil channel never blocks.
    Answer

    False! A nil channel always blocks. See Curious Channels, by Dave Cheney

  60. T/F? Calling a defined method on a nil-pointer panics immediately.
    Answer

    False! Unlike other languages that are more object-oriented, a Go method is essentially just a function which receives the 'receiver' as a regular (though somewhat obscured) argument. It is only when a field on the nil receiver is accessed that a panic will occur.

  61. T/F? Calling *testing.T.Fail() ends the currently executing test immediately.
    Answer

    False. The Fail method only marks the test as failed. The test will continue execution.

  62. T/F? Calling *testing.T.Fatal() panics immediately.
    Answer

    False. The Fatal method ends a test immediately, but without panicking.

  63. Simplify: log.Println(fmt.Sprintf("Hello, %s.", name))
    Answer

    log.Printf("Hello, %s.", name)