All About Go (Part Two)

In sequel to All About Go (Part One) in this article we will go over some additional Go concepts. These concepts include functions, looping, control flow (defer, panic, recover), pointers, gorotines, and channels.
Functions

Every Go application start by calling the main function. Every function starts with the func key word followed by the function name. The function name is written in camel case. Uppercase function names are public from the package, and lowercase are internal to the package (ex. main). The function then specifies what the parameter that are passed in through the brackets, specifying the parameter type. The function body is written inside of the curly braces.
Go has the ability to pass in a pointer as argument, which can provide performance benefits. However this can also lead to inadvertently changing the data in the calling function.
func main() {
printText("You are number", 1)
}func printText(text string, number int) {
fmt.Println(text, int)
}OR func main() {
printText("You are number", "1")
}//if parameters have the same type
func printText(text, number string) {
fmt.Println(text, number)
}OR//variadic parameter
func printText(str ...string) {
fmt.Println(str)
}
When returning in Go the return value type must be specified, as shown below. If there are multiple return values they are specified using parentheses (ex. (int, error)). In Go one can also return addresses of local variables.
Returnfunc main() {
fmt.Println("You are number", calculate(0))
}func calculate(num int) int {
return num + 1
}
Go also has anonymous functions (functions that do not have a name), which can be assigned to a variable.
Pointers
Pointers are declared by preceding the variable type with the dereference operator, and the location the pointer is pointing with the “address of operator” (&). When calling the variable it can be preceded by the dereference operator, which returns the value of that point in memory.
Go does not allow pointer arithmetic (unless you use the “unsafe package”).
func main() {
var a int = 10
var b *int = &a //b is the location in memory of a
fmt.Println(a, *b) //*b is the value in a's memory location
}ORfunc main() {
a := [3]int{5, 6, 7}
b := &a[0]
fmt.Println(a, *b)
}
Control flow
Defer
Using the defer key word allows part of the code’s execution to be delayed. Once the function exits the “defer” code is run. If all code is deferred the function runs in LIFO.
func main() {
fmt.Println("One")
defer fmt.Println("Three")
fmt.Println("Two")
}
Panic
Panic occurs when application is put into a state that it cannot recover from, this causes the function to stop executing (unrecoverable events). Although the function will stop executing it will run defer functions. If file can’t be open this is usually an error, not a panic.
Recover
If application panics it can be recovered using recover. This is only useful in defer functions with the recover key word. The current function will not run, however higher functions in call stack will.
Looping
In Go there is only one type of looping statement. The for loop is structured as shown below.
func main() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}//multiple initializers
func main() {
for i, j := 0, 0; i < 10; i, j = i+1, j+3 {
fmt.Println(i, j)
}
}//Go's while loop
func main() {
i := 0
for i < 5 {
fmt.Println(i)
i++
}
fmt.Println(i)
}//for range loop
//used for slices, arrays, strings, maps, and channels
func main() {
arr:= []int{1, 2, 3}
for key, value:= range arr {
fmt.Println(key, value)
}
}
Goroutines
To create a goroutine you use the go key word before invoking the function, which calls on the thread light weight abstraction over a thread.
WaitGroups allows go to wait for groups of goroutines to complete. There are several methods associated with WaitGroups these include add, wait, and done. Add method informs the go routine it has more routines to wait on, wait method blocks go routine it is called on until the gtourine is completed, and done method is used to notify that one of the goroutines is completed.
Mutexes & RWMutex are used to protect data, by making sure that only one goroutine is manipulating that data at a time.
func main() {
go printText() //main function is executing in a goroutine
//bad practice but necessary for printText to run
time.Sleep(100 * time.Millisecond)
}func printText() {
fmt.Println("Hello World!")
}OR//with WaitGroup
var wg = sync.WaitGroup{}func printText() {
var text = "Hello World!"
go func(text string){
fmt.Println(text)
wg.Done()
}(text)
wg.Wait()
}
Channels
Channels allow us to pass data between different goroutines and prevent memory sharing problems, and race conditions. This is done by synchronizing transmissions between goroutines with senders, and receivers as shown below. Channels can be designated to send only or receive only. Additionally buffer channels are available to block appropriate side (sender, receiver) to only execute when data is available.
ch := make(chan int) // first we create a channel to accept messages
ch <- message // Send message to channel
message := <- ch //receive message from a channel// Send only
ch <- int//Receive only
<- ch int//buffer channels
make(ch int, 20)