Several Fun Things to Know in Golang

Credits to Greyson Joralemon (https://unsplash.com/photos/9IBqihqhuHc)

There are several “fun things” that you need to know, this might help you to prevent going in pitfalls when writing golang program. These includes:

  1. Interface contains nil but not considered as nil
  2. Slice and Map are reference type
  3. Child Go routine and Parent
  4. Don’t use basic type string / int / others for context key

Look at the simple code below

var i interface{}
var pint *int
i = pint
fmt.Println(pint, pint == nil, i, i == nil)

It will print the output of

<nil> true <nil> false

While pint variable is a nil (a pointer to an int but never store an actual pointer), when it’s being stored in an interface, it’s returning false on nil comparison because Golang interface store two things, its type and its value. So it will store a type of *int, and value of nil. Interface will be considered as nil if both type and value is nil. So, be careful if you’re using nil comparison on some interface type… You can get non nil when it’s storing a pointer, even though the pointer is nil (not pointing to anything).

More details you can check this article
https://glucn.medium.com/golang-an-interface-holding-a-nil-value-is-not-nil-bb151f472cc7#:~:text=Under%20the%20hood%2C%20an%20interface,type%20and%20value%20are%20nil

based on https://blog.golang.org/maps. Golang maps and slice are reference type, therefore if you pass it into a function and do some modification to its content, the underlaying data will also changed, take a look at this code.

func sliceAndMap() {
mp := map[int]bool{
1: true,
2: true,
}
slc := []int{1,2}

modify(mp, slc)
fmt.Printf("%+v || %+v\n", mp, slc)}func modify(mp map[int]bool, slc []int) {
mp[2] = false
slc[0] = 10
}

When you’re running sliceAndMap() function, it will print

map[1:true 2:false]  ||  [10 2]

Since the “pointer” is being passed to the modify function, therefore any change to map and slice in that will change the map / slice also.

Consider this simple code

func main() {   goParent()
}
func goParent() { fmt.Println("Parent Start") go func() { fmt.Println("Child is done") }() // time.Sleep(5 * time.Millisecond) fmt.Println("Parent is done")}

If you run the goParent() function, it will print

Parent Start
Parent is done

So where is the child go routine? the child go routine is not yet running but the goParent function is done already, therefore it’s not yet executed. Uncommenting the time Sleep can make the “Child is done” to be printed since it gives some time to “pause” the parent so the child go routine can finish the execution before the parent dies / done. If you’re developing an API / web service application, most of the times you don’t need to do this pausing parent thing, since usually at the most top level of the program serving http server is a “never die” process, so the most top parent running, therefore the spawned child go routine can execute its task until all done.

What does it mean actually?

ctx := context.WithValue(ctx, key, value)

When creating a context, usually the intuitive way is using string / int as a key (as preferred). But it’s more recommended to use “your own key type” rather than using basic type. If you use for example “mykey” for the key parameter, on golang linter you will get message of

should not use basic type string as key in context.WithValue

It’s recommended to use your own type for the key to prevent key collision, even if it’s actually a string or an int for example, take a look at this

type CKRequestID intconst (  RequestIDContextKey CKRequestID = iota + 1)
func ContextKeyCollision() { ctx := context.WithValue(context.Background(), RequestIDContextKey, "bb") ctx = context.WithValue(ctx, 1, "cc") ctx = context.WithValue(ctx, 1, "ff") usingKey := ctx.Value(RequestIDContextKey) usingInt := ctx.Value(1) fmt.Println(usingKey, usingInt)}

It will print

bb ff

As you can see on that code, when we’re using an int of 1 as our key, if the context value key of 1 is being called again with different value, the context with key of 1 value’s is changed (It prints “ff” due to the latest assignment). But when we’re using the key type of CKRequestID, our own created type, its value is not overridden by the 1 as a key assignment even though actually RequestIDContextKey is equal to an int of 1. The idea is to use your own “type” so it will not collision when the context is extended by another function / process / package since the key type is unique, when a simple basic int or string is not unique (prone to collision via another process).

Fellow Software Engineer