Tag logo

Short notes about Golang

Oct 10, 2022/
#golang
/-9 min

Living Article is a term I coined that refers to a piece of writing which can be useful right now, but also has the potential to be enriched with additional content in the future.

Basics

  • Golang is a programming language that is similar to C but is easier to work with, compiles faster, and provides support for multithreads. It is mostly used in microservice applications.
  • You can use
    go doc fmt
    to see the doc for a package and
    go doc fmt.Println
    to see the doc for a method.
1func Fprint(w io.Writer, a ...any) (n int, err error)
2func Fprintf(w io.Writer, format string, a ...any) (n int, err error)
3func Fprintln(w io.Writer, a ...any) (n int, err error)
4func Print(a ...any) (n int, err error)
5func Printf(format string, a ...any) (n int, err error)
6func Println(a ...any) (n int, err error)
7func Sprint(a ...any) string
8func Sprintf(format string, a ...any) string
9func Sprintln(a ...any) string
10...

Hello World

  • The structure of the simplest Golang file is as follows (use
    go run main.go
    to run the code):
1// the organization of files is provided by packages.
2// files located in the same folder must have the same package.
3package main
4
5import "fmt"
6
7// the main function is the entry point of the program.
8// there can be a main function in each package.
9func main() {
10  fmt.Println("Hello World")
11}
  • There are various printing functions. Those starting with
    Print
    go to the console and return the number of bytes. Those with
    Fprint
    go to an external source like a file or a browser. Those with
    Sprint
    store the output in a character buffer, essentially converting it to a variable.
1package main
2
3import "fmt"
4
5func main(){
6  fmt.Println("Hello!")
7  fmt.Print("Hello!\n")
8  fmt.Printf("Hi! My name is %s. I have lived in %s for %d years. They say the weather is amazing, which is %t", "Enes", "Istanbul", 23, true)
9
10  // returns "Hello Enes!" but doesn't print to console.
11  result := fmt.Sprintf("Hello %s!", "Enes")
12}

Variables

  • The data types are as follows:
    • Integer: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
    • Float: float32, float64
    • String: string
    • Boolean: bool
    • Null: nil
  • When defining variables, we can proceed in multiple ways. We can either give the variable a type or let Golang infer it.
1// first way
2count := 10
3// second way
4var age = 21
5// third way
6var minute int = 60
7// fourth way
8const MAX_COUNT = 10
9// fifth way
10var city string
11city = "Istanbul"
  • We can use
    reflect.TypeOf()
    to see the type of a variable.
1stringVar := "string"
2intVar := 10
3floatVar := 1.2
4boolVar := false
5arrayVar := []string{"foo", "bar", "baz"}
6objectVar := map[string]int{"apple": 23, "tomato": 13}
7nilVar := nil
8
9fmt.Println(reflect.TypeOf(stringVar))
10fmt.Println(reflect.TypeOf(intVar))
11fmt.Println(reflect.TypeOf(floatVar))
12fmt.Println(reflect.TypeOf(boolVar))
13fmt.Println(reflect.TypeOf(arrayVar))
14fmt.Println(reflect.TypeOf(objectVar))
15fmt.Println(reflect.TypeOf(nilVar))
string
int
float64
bool
[]string
map[string]int
  • In Golang, there are methods with the same name for variable type conversions.
1floatNumber := 5.2
2intNumber := int(floatNumber)
3fmt.Println(intNumber)
5

Conditional Statements and Loops

  • In the
    if-else
    statement, unlike in JavaScript, parentheses are not used.
1if num > 50 {
2  // ...
3} else if num > 20 {
4  // ...
5} else {
6  // ...
7}
  • There is a special syntax to execute a code when the function runs without errors.
1// the 'err' variable can only be accessed within this block.
2if err := someFunction(); err != nil {
3  fmt.Println(err.Error())
4}
  • There are two uses of the
    switch
    statement. If we want to advance to the next case, we can use
    fallthrough
    .
1// first way
2var status string = "ERROR"
3
4switch status {
5  case "WARN":
6    // ...
7  case "ERROR":
8    // ...
9  default:
10    // ...
11}
12
13// second way
14var num int = 25
15
16switch {
17  case num < 50:
18    // ...
19  case num < 100:
20    // ...
21  default:
22    // ...
23}
  • There are different usages of the for loop structure. Golang does not have a built-in while loop structure.
1for i := 1; i <= 100; i++ {
2  fmt.Println(i)
3}

Function

  • We use the
    func
    keyword to define a function. Unlike TypeScript, we don't use a colon when defining the parameter or return type.
1package main
2
3import (
4	"fmt"
5	"time"
6)
7
8func main() {
9  fmt.Println(getAgeByYear(1999))
10}
11
12func getAgeByYear(yearOfBirth int) int {
13  return time.Now().Year() - yearOfBirth
14}
  • We can return multiple values in functions.
1func getNumArray() (int, int)  {
2  return 4, 5
3}
  • We can use the
    ...
    operator to collect a variable number of arguments as a collection. These functions are called variadic functions.

The range keyword returns the index and the element. We use '_' because we won't use the index, otherwise we get an 'unused variable' error.

1func printNames(names ...string) {
2  for _, name := range names {
3    fmt.Println(name)
4  }
5}

Array

  • Arrays must have either a fixed size or a size specified at initialization. Ex,
    [5]int != [4]int
    . We can also use the
    ...
    operator if we want the size to be automatically calculated.
1var nums [5]int
2fmt.Println(nums)
3
4var nums2 [2]float64 = [2]float64{2.2, 3.8}
5fmt.Println(nums2)
6
7names := [4]string{"Ali", "Veli"}
8fmt.Println(names)
9
10names2 := [...]string{"Ali", "Veli", "Ahmet"}
11fmt.Print(names2)
12fmt.Println(" - type: ", reflect.TypeOf(names2))
[0 0 0 0 0]
[2.2 3.8]
[Ali Veli ]
[Ali Veli Ahmet] - type: [3]string
  • We can use
    range
    to iterate over the elements of arrays.
1nums := [5]int{1, 2, 3, 4, 5}
2
3for _, num := range nums {
4  fmt.Print(num)
5}
12345
  • We can also use
    range
    to iterate over string values. However, it returns the byte value instead of the character. Therefore, we need to convert it to string.
1var mySentence = "Sentence"
2
3for index, letter := range mySentence {
4	fmt.Println("Index:", index, "Byte:", letter, "Letter:", string(letter))
5}
Index: 0 Byte: 83  Letter: S
Index: 1 Byte: 101 Letter: e
Index: 2 Byte: 110 Letter: n
Index: 3 Byte: 116 Letter: t
Index: 4 Byte: 101 Letter: e
Index: 5 Byte: 110 Letter: n
Index: 6 Byte: 99  Letter: c
Index: 7 Byte: 101 Letter: e
  • Dynamic-sized arrays can be defined.
    make(type, len, cap)
    is used to create objects of type
    slice
    ,
    map
    , or
    channel
    .

If we use the expression

var mySlice []int
without initializing, it will cause an error because it cannot know the size it needs to allocate in memory.

1// first param: item type
2// second param: starting size
3// third param: max capacity
4var mySlice []int = make([]int, 5, 10)
5
6fmt.Println(len(mySlice))
7fmt.Println(cap(mySlice))
5
10
  • To slice an array, we use the syntax of two colons.
1nums := [5]int{1, 2, 3, 4, 5}
2someNums := nums[2:4]
3fmt.Println(reflect.TypeOf(someNums))
4fmt.Println(someNums)
[]int
[3, 4]
  • To add an element to a dynamic array, we use the built-in
    append()
    method. It takes the array and new elements.
1var arr []int = make([]int, 3, 10)
2newArr := append(arr, 5, 2, 7, 12)
3
4fmt.Println(newArr)
[0 0 0 5 2 7 12]

Map

  • It is a data type that holds key-value pairs, similar to objects in JavaScript. We can define it in three different ways.
1// first way
2userInfo1 := map[string]string{
3  "name": "Cem",
4  "surname": "Yılmaz",
5}
6fmt.Println(userInfo1)
7
8// second way
9userInfo2 := map[string]string{}
10userInfo2["name"] = "Cem"
11userInfo2["surname"] = "Yılmaz"
12fmt.Println(userInfo2)
13
14// third way
15var userInfo3 map[string]string = make(map[string]string)
16userInfo3["name"] = "Cem"
17userInfo3["surname"] = "Yılmaz"
18fmt.Println(userInfo3)
map[name:Cem surname:Yılmaz]
map[name:Cem surname:Yılmaz]
map[name:Cem surname:Yılmaz]
  • Functions and objects usually return two values. The second value indicates whether the operation was successful or not and can also be used to check if a key exists in an object.
1userInfo := map[string]string{
2  "name":    "Cem",
3  "surname": "Yılmaz",
4}
5
6city, hasCityKey := userInfo["city"]
7fmt.Println("value:", city)
8fmt.Println("hasCityKey:", hasCityKey)
9
10// if we want to execute a code only when the key is exist
11userInfo["city"] = "Istanbul"
12if city, hasCityKey := userInfo["city"]; hasCityKey {
13  fmt.Printf("%s live in %s", userInfo["name"], city)
14}
value:
hasCityKey: false
Cem live in Istanbul
  • To remove a key from map, we can use
    delete()
    .
1userInfo := map[string]string{
2  "name":    "Cem",
3  "surname": "Yılmaz",
4}
5
6delete(userInfo, "surname")
7fmt.Println(userInfo)
map[name:Cem]

Package

  • We can create our own package.
1package utils
2
3func Add(nums ...int) int  {
4  total := 0
5
6  for _, num := range nums {
7    total += num
8  }
9
10  return total
11}
1package main
2
3import (
4  "fmt"
5  "{workspaceFolderName}/utils"
6)
7
8func main()  {
9  utils.Add(2, 21)
10}

We can give a name to an import using the

math "{workspaceFolderName}/utils"
syntax. Otherwise, it is accessible with the package name.

Unit Testing

  • Tests of files should be located in the same directory and named with a suffix of
    _test
    added to the original file name.
1package utils
2
3func Average(nums ...int) int {
4  total := 0
5
6	for _, num := range nums {
7    total += num
8	}
9
10	return total
11}
1package utils
2
3import (
4	"testing"
5)
6
7func TestAverage(t *testing.T) {
8  expected := 4
9  actual := Average(3, 2)
10
11  if actual != expected {
12    t.Errorf(
13      "Add function does not add up: Expected: %d, Actual: %d",
14      expected,
15      actual
16    )
17  }
18}

Resources