Below you will find a quick, and short cheatsheet, written with simple examples, and easy to understand words.
Declaring variables of different types:
{% highlight go %} var i int var s string var b byte var bo bool // Boolean: true or false var ch rune // Go’s equivalent of a single character. var sSlice []string // This creates a slice of strings. var sArr [3]string // This creates an array with a length of 3 strings. var mp map[string]int // Creates a map with string type as key, and int type as value. {% endhighlight %}
Note: For the map declared above, when doing write operations, Go panics: panic: assignment to entry in nil map. So an initialization is required before the first write.
Either mp = map[string]int{} or mp = make(map[string]int) will work.
Constants
{% highlight go %} const n = 20 // Go supports untyped constants. const num int = 5 const str string = “const” const st = “this is a string” {% endhighlight %}
Declaration/Initialisations that are only valid inside a function.
{% highlight go %} func declare() { // All functions start with “func” keyword. str := "" // This way of initialization is only valid inside a function. num := 3 isTrue := true c := ‘a’ // creates a rune s := []string{} // creates a slice of string type. hashMap := map[string]int{} // Creates a map of string as key, and int as value. } {% endhighlight %}
Note: In Golang, all declared variables must be used, else compiler will throw an error.
Example of the error: declared and not used: str
Functions
In Golang, functions are declared using the func keyword. Below a and b are parameters with an int type.
{% highlight go %}
func add(a int, b int) int { // int indicates the type of the return value.
// function parameters of the same type can also be written like this:
// func add(a , b int) int {
return a + b
}
func nums() (int, int) { // A way to indicate multiple return values/types. return 3, 4 } {% endhighlight %}
NOTE: Function names that start with lowercase (example: func add()) act like private methods of a package. And function names that start with an uppercase (example: func Add()) act like public methods, and are automatically exported.
Variadic functions
Variadic functions can accept a variable number of arguments. In Go, three dots(…) followed by a type, is used as a parameter to signify a variadic function.
{% highlight go %} func variadic(nums …int) { sum := 0 for _, n := range nums { sum += n } fmt.Println(sum) }
variadic(3, 4, 5) variadic(1,2) {% endhighlight %}
Defer
An important concept/keyword to understand when learning functions in golang is the concept of “defer”.
defer works quite similar to what it means: it puts off an event to a later time.
defer runs just before the function returns.
{% highlight go %}
func deferExample() {
defer fmt.Println(“print this later”) // prints this later, when the function is about to return.
fmt.Println(“print this now”) // prints this first.
}
{% endhighlight %}
Slice methods
{% highlight go %} func sliceMethods() { arr := []int{1, 2, 3, 4} arr = append(arr, 5) // appending to a slice val := arr[0] // getting the value at 0th index arr[2] = 4 // setting the value as 4 at 2nd index of the slice.
// Create a new slice from a specific range of the original slice.
newSlice := arr[0:2] // Here, 0 is start index, 2 is end index. Value at 2nd index is not included.
length := len(newSlice) // len() gives the length of the slice.
} {% endhighlight %}
Map methods
{% highlight go %} func mapMethods() { mp := map[string]int{} mp[“a”] = 1 // set a key/value if v, ok := mp[“a”]; ok { // checks if the key exists. // do something with the value. } delete(mp, “a”) // deletes “a” from the map “mp” clear(mp) // deletes all entries from the map. length := len(mp) // Total number of entries in the map. } {% endhighlight %}
Loops
In Golang, loops are done using the for and range keyword
{% highlight go %}
func loops() {
for i := 0; i < 5; i++ {
// do something
}
n := 5
for range n { // range lets you loop over things easily.
// do something
}
// An infinite loop, similar to "while" from other languages.
for {
if n > 5 {
break
}
}
arr := []int{1, 2, 3, 4}
for idx, val := range arr {
// here idx is an index of the current item in arr.
// val is the value at that index.
}
mp := map[string]int{
"a": 0,
"b": 1,
}
for k, v := range mp {
// k: key, v: value
}
} {% endhighlight %}
Conditionals:
if/else statements
{% highlight go %} func conditionals() { a := 0 b := 1 if a < b { // do something } else if b > a { // do something } else { // do something } } {% endhighlight %}
Switch/case
{% highlight go %} func cases() { a := 5 switch a { case 6: // do something if a == 6 case 7: // do something if a == 7 default: // by default do this, if other cases do not match. } } {% endhighlight %}
Pointers and references
{% highlight go %} func pointer() { num := 4 p := &num fmt.Println(p) // prints the memory address of num. }
func changeValue() { num := 4 passWithoutPointerAndChange(num) fmt.Println(num) // still prints 4 passWithPointerAndChange(&num) // This changes the value to 5. // When passed with the pointer, it modifies the value at that address. fmt.Println(num) // now prints 5
// Same applies to other data types.
arr := []int{}
tryChangingArrWithoutPointer(arr)
fmt.Println(arr) // prints an empty slice [].
tryChangingArrWithPointer(&arr)
fmt.Println(arr) // prints [3].
}
func passWithoutPointerAndChange(num int) { num = 5 // change is made to the copy, not the original }
func passWithPointerAndChange(num *int) { // * is used infront of num to dereference the pointer. // In simple words, go to the memory address stored in num, // and give the value that lives there. In this case, 4. *num = 5 // modifies the value at that address. }
func tryChangingArrWithoutPointer(arr []int) { arr = append(arr, 3) // append is used this way in golang. }
func tryChangingArrWithPointer(arr *[]int) { *arr = append(*arr, 3) // append is used this way in golang. } {% endhighlight %}
Struct
Struct, in easy words, is a way to create your own type with multiple fields.
{% highlight go %}
type Card struct { Name string Number string CCV int }
// Initialize Card. c := &Card{}
// This is a way to create a method for a struct in Golang. // Go does not have classes. func (c *Card) Add() { c.Name = “Rob Kim” // struct fields are accessed using a dot c.Number = “123456890” c.CCV = 532 }
// This can be called like this: c.Add() // Calls Add method on the card created above. {% endhighlight %}
Interface
In simple words, an interface is a contract that says: any type that has these methods automatically satifies this interface type. In golang, there is no explicit declaration for interfaces.
Below is a simple “Animal” example to showcase interface:
{% highlight go %} type Animal interface { Speak() }
type Cat struct { Name string }
func (c *Cat) Speak() { fmt.Printf(“Meow says %s\n”, c.Name) }
type Dog struct { Name string }
func (d *Dog) Speak() { fmt.Printf(“Woof says %s\n”, d.Name) }
func main() { c := &Cat{ Name: “Zing”, }
d := &Dog{
Name: "Max",
}
animals := []Animal{c, d} // Here, both Cat and Dog have the same method Speak(), which is a method of the Animal interface.
for _, a := range animals { // The types Cat and Dog satisfy the Animal interface type.
a.Speak()
}
} {% endhighlight %}
Generics
Generics allows you to write functions or data structures that work with any data type.
Simple example: {% highlight go %} func printSlice[T any](s []T) { for _, v := range s { fmt.Println(v) } }
// Now you can call this generic function printSlice with any data type. nums := []int{1, 2, 3, 4} printSlice(nums)
s := []string{“a”, “b”, “c”} printSlice(s) {% endhighlight %}
Concurrency/Goroutines:
Goroutines are a way to handle concurrent tasks in Go. It allows multiple tasks to be executed at the same time without blocking each other.
{% highlight go %} func add() { time.Sleep(2 * time.Second) fmt.Println(“Adding completed”) }
func subs() { time.Sleep(2 * time.Second) fmt.Println(“Subtracting completed”) }
go add()
go subs()
fmt.Println(“I could be doing something else while my goroutines are executing concurrently”)
time.Sleep(3 * time.Second) // This is to not let the main function return immediately.
{% endhighlight %}
NOTE: In a real case, if the above code did not have time.Sleep(3 * time.Second) on the last line, the main function would return immediately after printing the last line,
instead of waiting for all the goroutines to finish. To deal with this, either channels, or a sync package is generally used in real case scenarios.
Channels
In easy to understand words, a channel is like a pipe through which multiple goroutines can share data, without worrying about mutex locks, or deadlocks.
{% highlight go %}
func main() {
ch := make(chan int, 2) // Makes a channel of buffer length 2, that can hold data of type int.
// ch := make(chan int) // You could write this too. This would create a channel without any specific length/size.
go add(5, 4, ch)
go add(2, 5, ch)
res1 := <-ch // receive the value from the channel, and assign it to res1. The program waits here, until it receives the value.
res2 := <-ch // receive the value from the channel
fmt.Println(res1)
fmt.Println(res2)
}
func add(a, b int, ch chan int) { time.Sleep(2 * time.Second) ch <- a + b // send the value to the channel } {% endhighlight %}
Mutex
Mutex is a mutual exclusion lock.
Example use case: Mutex is generally used to update a shared slice across multiple goroutines.
Mutex, and other items from the sync package are generally used together. These include: WaitGroup and its methods. This is a way in Go to avoid deadlocks, and race conditions.
A simple example is shown below:
{% highlight go %} import ( “fmt” “sync” “time” )
type Res struct { values []int mu sync.Mutex // To ensure only one goroutine access this resource at a time. }
func NewRes() *Res { return &Res{ values: []int{}, } }
func main() { var wg sync.WaitGroup // A way to wait for all goroutines to finish their tasks.
r := NewRes()
wg.Add(2) // Add goroutines to the wait group.
go add(5, 4, r, &wg)
go add(1, 3, r, &wg)
wg.Wait() // Wait for all the goroutines to finish their tasks.
for _, v := range r.values {
fmt.Println(v)
}
}
func add(a, b int, r *Res, wg *sync.WaitGroup) { defer wg.Done() // Mark this goroutine as done, when this function is about to returns. time.Sleep(2 * time.Second) sum := a + b r.mu.Lock() // Lock this resource. r.values = append(r.values, sum) r.mu.Unlock() // Unlock this resource. } {% endhighlight %}