DEV Community

Cover image for The Most Difficult Syntax in Go for a Programming Newbie
Yulin Chen
Yulin Chen

Posted on

The Most Difficult Syntax in Go for a Programming Newbie

This is a soft introduction to Go for those who have never coded in a typed language before. Go is a simple language and exposes some syntax like pointers which are hidden in other higher level languages. You might have heard of concurrency and channels in passing and you will come across these concepts in Go. There are also some interesting types in Go which can improve code performance if used in right places, i.e. empty interface and struct.

Quick Links:
Pointers
Empty Interface
Empty Struct
Concurrency


Pointers

Variables are passed as arguments into a function by their values in Go, meaning that the variable themselves are not modified by the function. A new copy of the variables are created and stored in separate memory spaces the moment they are passed as arguments. To actually change the variables through the operations in the function, we must pass the variables by pointers by adding a ampersand in front of the variables.

Let's see some examples to illustrate the concept

Pass by values

Run the following code, you'll find the variable remain unchanged

package main

import "fmt"

func changeNumber(number int){
        number = 5
}

func main() {
    i := 40
    changeNumber(i)
    fmt.Println(i)
}

Enter fullscreen mode Exit fullscreen mode

Pass by pointers

Notice the two symbols used here:
* applied on a pointer denotes the underlying value of the pointer, which allows us to change the original value the pointer stored.

* applied to a Go type, denotes a pointer of that type.

& applied on a variable generates a pointer of that variable.

package main

import "fmt"

func changeNumber(number *int){ //pass pointer type *int, which is a pointer to the type int
        *number = 5 // set number through the pointer
}

func main() {
    i := 40
    changeNumber(&i) //pointer to variable i
    fmt.Println(i)
}
Enter fullscreen mode Exit fullscreen mode

Empty Interface

An object is of a certain interface type if it implements all of the methods detailed in the interface. An empty interface is an interface that specifies zero methods. Therefore any type implements the zero method empty interface.

type empty interface{} // named empty interface
var empty interface{} // anonymous empty interface
Enter fullscreen mode Exit fullscreen mode

This is useful where a function does not know the type of the input parameters, for example:

package main

import "fmt"

func printAnything(something interface{}){ 
        fmt.Printf("%v is of type %T\n", something, something)
}

func main() {
    // declare variable "anything" to be an empty interface type
    var anything interface{} 
    // assign 1 to the variable, which is of type int
    anything = 1
    printAnything(anything)
    // reassign the variable with a different type, allowed because "anything" implements the empty interface
    anything = "word"
    printAnything(anything)
}
Enter fullscreen mode Exit fullscreen mode

Empty Struct

A struct in Go is a way to construct an object, a struct contains named fields each has a name and a type.

type empty struct{} // named empty struct
var empty struct{} // anonymous empty struct
Enter fullscreen mode Exit fullscreen mode

An empty struct is special in rust because it has no memory cost:

fmt.Println(unsafe.Sizeof(empty)) //size 0
Enter fullscreen mode Exit fullscreen mode

This makes an empty struct a perfect option for maps that checks the existence of an element or filter through an duplicated array, or make a synchronisation channel:

package main

import "fmt"

func main() {
        // an array of duplicated integers
    nums := []int{1, 2, 3, 4, 5, 1, 2, 3}
        // map to stored unique integers from the array
    checkNumExist := make(map[int]struct{})
    for _, num := range nums {
        checkNumExist[num] = struct{}{}
    }
    for key := range checkNumExist {
        if _, exist := checkNumExist[key]; exist {
            fmt.Printf("num %d exists!", key)
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Concurrency

Normally you would expect your code to execute from top to bottom in the exact order you've written it. Concurrency when the code execution is out of order, multiple computations happening at the same time to get a faster result. If the number of tasks is more than the number of processors available, time slicing is a technique used by the processors to simulate concurrency by switching between tasks. A common concurrency related problem is race condition, whereby the relative timing of the concurrent modules can return an inconsistent result if you were running the program non-concurrently. They are hard to debug because the reproducibility of the same outcome is hard.

In terms of communication, there are two models for concurrent programming:

Shared Memory

Concurrent modules have the same access to the same object.

Message Passing

Concurrent modules communicate through channels, sending and receiving messages from and to channels.

Make Channels

In Go, the syntax for making channels:

ch1 := make(chan int) //unbuffered channel, takes one message
ch2 := make(chan int, 5) //buffered channel, takes multiple messages
Enter fullscreen mode Exit fullscreen mode

Sending to an unbuffered channel will be blocked until the message has been received. Sending to a buffered channel on the other hand is a non-blocking operation as long as the channel hasn't used up its full capacity.

Write a message to a channel:

ch1 <- 1
Enter fullscreen mode Exit fullscreen mode

Receive/read a message from a channel:

msg := <- ch1
msg, closed := <- ch1 // closed is true if channel closed
Enter fullscreen mode Exit fullscreen mode

Open a thread

To run a function in a separate thread, add the keyword "go" before you call the function. All goroutines share the same memory space, to avoid race conditions make sure you synchronise memory access e.g. by using "sync" package.

package main
import (
"fmt"
)

func main(){
// make an int type channel
ch := make(chan int)

// anonymous goroutine
go func(){
    fmt.Println(<- ch) // print the received
}()

ch <- "Hello World!" //send to channel

}
Enter fullscreen mode Exit fullscreen mode

This is a simple example on a goroutine, pay attention to the order of the goroutine, it comes before the send. This is because for an unbuffered channel, communications are sync, use buffered channels for async.

Here's another example on coordinating goroutines, taken from riptutorial:

func main() {
    ch := make(chan struct{})
    go func() {
        // Wait for main thread's signal to begin step one
        <-ch

        // Perform work
        time.Sleep(1 * time.Second)

        // Signal to main thread that step one has completed
        ch <- struct{}{}

        // Wait for main thread's signal to begin step two
        <-ch

        // Perform work
        time.Sleep(1 * time.Second)

        // Signal to main thread that work has completed
        ch <- struct{}{}
    }()

    // Notify goroutine that step one can begin
    ch <- struct{}{}

    // Wait for notification from goroutine that step one has completed
    <-ch

    // Perform some work before we notify
    // the goroutine that step two can begin
    time.Sleep(1 * time.Second)

    // Notify goroutine that step two can begin
    ch <- struct{}{}

    // Wait for notification from goroutine that step two has completed
    <-ch
}
Enter fullscreen mode Exit fullscreen mode

Side Notes on Go Map

The Go's implementation of a map is a hashmap. A hashmap uses a hash function that takes a key and returns a deterministic value derived from the key. Each item in the hashmap gets an unique index, so called a hash. The hashmap data structure is an array of buckets, each contains a pointer/unique index to an array of key-value pairs. The map methods are converted to calls to the runtime during code compilation. To read more about Go maps click here.


Useful resources & References

Tour of Go
Net Ninja Youtube playlist
Concurrency - MIT
Concurrency - RIP tutorial

Discussion (0)