Sunday, 28 November 2021

DO NOT use the return of append function to create new slice in Go

For Golang developers, it is very common to use append function to append new elements into slice. The common syntax is like below:

    var slice := make([]int, 0)
    slice = append(slice, 123)
And for some requirements, you may want to create a new slice based on an existing slice. We know the below the code will work perfectly.
    slice := []int{1,2,3}
    newSlice := make([]int, 0)
    newSlice = append(newSlice, append(slice, 4)...)
Below is complete example code to show the result:
package main

import (
    "fmt"
)

func main() {
    // here create the origin slice with cap 10 to make sure that
    // the slice is big enough to hold more data
    slice := make([]int, 0, 10)
    slice = append(slice, []int{1, 2, 3}...)
    newSlice := make([]int, 0)
    newSlice = append(newSlice, append(slice, 4)...)
    fmt.Println(slice)
    fmt.Println(newSlice)
    fmt.Printf("The address of slice: %v\n", &slice[0])
    fmt.Printf("The address of newSlice: %v\n", &newSlice[0])
}
The result is:
[1 2 3]
[1 2 3 4]
The address of slice: 0xc000014050
The address of newSlice: 0xc00007a000
You can see the two slices have different addresses.
However you may think if we can simplify the code by appending value to the origin slice and assign the return value to the newSlice variable like below:
    slice := []int{1,2,3}
    newSlice := make([]int, 0)
    newSlice = append(slice, 4)
This looks simpler and cleaner, right? NO, this won't work! Let's see an example:
package main

import (
    "fmt"
)

func main() {
    // here create the origin slice with cap 10 to make sure that
    // the slice is big enough to hold more data
    slice := make([]int, 0, 10)
    slice = append(slice, []int{1, 2, 3}...)
    newSlice := make([]int, 0)
    newSlice = append(slice, 4)
    fmt.Println(slice)
    fmt.Println(newSlice)
    fmt.Printf("The address of slice: %v\n", &slice[0])
    fmt.Printf("The address of newSlice: %v\n", &newSlice[0])
}
The result is:
[1 2 3]
[1 2 3 4]
The address of slice: 0xc00010a000
The address of newSlice: 0xc00010a000
See newSlice is pointing to the same address of slice?

This is because append function does not guarantee that it always returns a new slice. It only reallocate a new slice when the capacity of the slice in the first parameter is not enough to hold more data. So if you are expecting to use append function to get a new slice like above, you will probably mess up the origin slice and get the wrong output.
So to be safe, if you want to get a new slice based on an existing slice, you use the code below:
    slice := []int{1,2,3}
    newSlice := make([]int, 0)
    newSlice = append(newSlice, append(slice, 4)...)
Or below code if it is clearer.
    slice := []int{1,2,3}
    newSlice := make([]int, 0)
    newSlice = append(newSlice, slice...)
    newSlice = append(newSlice, 4)
I encountered this problem when I am practicing LeetCode problem 797



Wednesday, 17 November 2021

How to add optional arguments in Go function without impacting the existing function calls

Unlike C# you can put "Optional" keyword to add optional arguments in function, there is no direct way to create optional parameters in Go functions. And there is no method overloading in Go either. Even so, if you really need to add optional arguments to an existing function, maybe because this function is in a common library which is called by different software modules and you don't want to update all the modules for this new argument change, there is still a way to implement optional arguments thanks to variadic arguments in Go functions.

Variadic arguments allow developer to put any number of trailing arguments as the input of functions. For example:

package main

import (
  "fmt"
)

func main() {
  fmt.Printf("1 + 2 = %d\n", add(1, 2))
  fmt.Printf("1 + 2 + 3 + 4 + 5 = %d\n", add(1, 2, 3, 4, 5))
}

func add(args ...int) int {
  sum := 0
  for i:=0; i < len(args); i++ {
    sum += args[i]
  }
  return sum
}
The output is:
1 + 2 = 3
1 + 2 + 3 + 4 + 5 = 15
With the help of variadic arguments, you can easily to implement optional arguments. For example, you have a function to generate characters in a common library for a game development. This function can generate characters with name and sex for now. 
package main

import (
  "fmt"
)

func main() {
  createCharactor("Bertram", "Male")
}

func createCharactor(name string, sex string) {
  fmt.Println("---------- New Charactor ----------")
  fmt.Printf("Name is: %s\n", name)
  fmt.Printf("Sex is: %s\n", sex)
  fmt.Println("------ New Charactor Created ------")
}
The output is: 
---------- New Charactor ----------
Name is: Bertram
Sex is: Male
------ New Charactor Created ------
Now you want to add age argument to this function without impacting the existing function call. You can do like following:
package main

import (
  "fmt"
)

func main() {
  createCharactor("Bertram", "Male")
  fmt.Println()
  createCharactor("Kai", "Male", 15)
}

func createCharactor(name string, sex string, age ...int) {
  fmt.Println("---------- New Charactor ----------")
  fmt.Printf("Name is: %s\n", name)
  fmt.Printf("Sex is: %s\n", sex)
  if len(age) > 0 {
    fmt.Printf("Age is: %d\n", age[0])
  }
  fmt.Println("------ New Charactor Created ------")
}
The output is:
---------- New Charactor ----------
Name is: Bertram
Sex is: Male
------ New Charactor Created ------

---------- New Charactor ----------
Name is: Kai
Sex is: Male
Age is: 15
------ New Charactor Created ------
But the above code is not reusable to add other arguments with different data types. For example, what if you need to add title, email later? So in order to make the variadic argument more generic, we can use "interface{}" type as the variadic argument which gives the ability to add more arguments with different data types.

According to this, the above code can be refactored as below:
package main

import (
  "fmt"
)

func main() {
  createCharactor("Bertram", "Male")
  fmt.Println()
  createCharactor("Kai", "Male", 15, "Software Engineer", "kai@gmail.com")
  fmt.Println()
	
}

func createCharactor(name string, sex string, args ...interface{}) {
  fmt.Println("---------- New Charactor ----------")
  fmt.Printf("Name is: %s\n", name)
  fmt.Printf("Sex is: %s\n", sex)
  if len(args) > 0 {
    fmt.Printf("Age is: %d\n", args[0].(int))
    fmt.Printf("Tile is: %s\n", args[1].(string))
    fmt.Printf("Email is: %s\n", args[2].(string))
  }
  fmt.Println("------ New Charactor Created ------")
}
The output is:
---------- New Charactor ----------
Name is: Bertram
Sex is: Male
------ New Charactor Created ------

---------- New Charactor ----------
Name is: Kai
Sex is: Male
Age is: 15
Tile is: Software Engineer
Email is: kai@gmail.com
------ New Charactor Created ------

However, this solution does not convey the meaning of the optional arguments. So you either put a comment above the header to specify what it means for each optional argument, or comment where you call the functions, otherwise it will become very difficult to understand the code. So this solution is not recommended unless you have a very tight deadline to meet the delivery date and really have no time to refactor code.  

Difference between "docker stop" and "docker kill"

 To stop a running container, you can use either "docker stop" or "docker kill" command to do so. Although it seems doin...