Go - Types, variables, functions and classes

2018-11-18

Go - Types, variables, functions and classes

When learning a new programming language, I think that knowing its types is very important. They are the tools that you be working with, and you should know them: strengths and use cases, conventions... Among other things, variables and functions are basic too, even if they can get complex. Then we can find some 'advanced' concepts in classes and testing, for example. And which I think it makes a lot of difference is to know about the standard library, there is so many power in there... Here we will start to look at types, variables, functions and classes in Go: how we define them, how we assign them, etc. Let's dive in!

This is not intended as an exhaustive guide, it will be a bunch of concepts that I'm learning in Go, as I explore it.

Basic Types

As far as I know, these are the basic types of Go:

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32
     // represents a Unicode code point

float32 float64

complex64 complex128

There is a lot of detail here if you are in the numeric world but for now bool, string, int and float64 (higher precision than 32) are our friends. These are true or false, strings, numbers and "decimals".

Variables

In Go, there are different ways to specify variables. There is a snippet of code that defines variables in many ways:

 1 package main
 2 
 3 import "fmt"
 4 
 5 var completed = true
 6 var path, isCommon, odds = "/home/user/file.txt", false, 0.7
 7 
 8 var (
 9    roundsLeft, roundsCompleted int = 2, 2
10    float                       float64
11    str                         = "Hello!"
12 )
13 
14 func main() {
15    rarity, rarityID := "Common", 1
16    fmt.Println(rarity, rarityID)
17    fmt.Println(float)
18 }
$ go run gitlab.com/hectormartinez/variables
Common 1
0

There are a lot of things going here.

  • Line 5: definition without type using the var keyword.

  • Line 6: multiple definition without type using the var keyword.

  • Line 9: multiple definition with type using a var block.

  • Line 10: definition without value using a var block.

  • Line 11: definition without type using a var block.

  • Line 15: multiple definition without type avoiding the var using :=.

So here we learn that:

  • Go can infer types from variable definitions.

  • We can pack definitions into var blocks.

  • We can avoid the var keyworkd using :=.

  • We can define multiple variables with different types in the same line.

  • We can define a variable without value. In this case, Go will assign it a default value. 0 for floats and ints, '' (empty string) for strings and false for booleans.

More complex types

After the basics, there are more complex types like struct, array, slice and map.

struct

A struct is like a Namespace in Python, is a collection of keys and values. You can access its values using a dot and the key name.

package main

import "fmt"

// Item contains itemID, name and description
type Item struct {
   itemID      int
   name        string
   description string
}

func main() {
   ruby := Item{0, "Ruby", "Red gem"}
   fmt.Println(ruby.itemID)
   fmt.Println(ruby.description)
   fmt.Println(ruby.name)
}
$ go run gitlab.com/hectormartinez/struct
0
Red gem
Ruby

array

An array is a list of things which size is immutable. However, all its elements should be of the same type.

package main

import "fmt"

func main() {
   var mylist [10]int
   mylist[1] = 1
   mylist[3] = 3
   mylist[5] = 5
   fmt.Println(mylist)

   gems := [3]string{"Ruby", "Diamond", "Amethyst"}
   fmt.Println(gems)
}
$ go run gitlab.com/hectormartinez/array
[0 1 0 3 0 5 0 0 0 0]
[Ruby Diamond Amethyst]

slice

A slice is a flexible view of an array.

package main

import "fmt"

func main() {
   letters := [4]string{"a", "b", "c", "d"}
   fmt.Println(letters, len(letters))

   twoLetters := letters[:2]
   fmt.Println(twoLetters, len(twoLetters), cap(twoLetters))

   numbers := []int{1, 2, 3, 4, 5}
   fmt.Println(numbers, len(numbers), cap(numbers))

   numbers = numbers[1:2]
   fmt.Println(numbers, len(numbers), cap(numbers))

   numbers[0] = 27
   fmt.Println(numbers, len(numbers), cap(numbers))
}
$ go run gitlab.com/hectormartinez/slice
[a b c d] 4
[a b] 2 4
[1 2 3 4 5] 5 5
[2] 1 4
[27] 1 4

We can define them from an array, as the letters example, or define it as literals, as the numbers example. When we define them, the second number inside the squared brackets is not included, so in the numbers = numbers[1:2] will be only one number. Then, slices have length and capacity. The length is self-explanatory, about the capacity, it is the size or the underlying array. Also, we can modify the slices and the underlying array will be modified too.

map

A map is a like a dictionary in Python, is a collection of keys and values. However, while a struct's fields can be accessed with dots, map fields should be accessing using square brackets:

package main

import "fmt"

// Item contains the coordinates X and Y
type Item struct {
   itemID      int
   name        string
   description string
}

func main() {
   ruby := Item{0, "Ruby", "Red gem"}
   diamond := Item{1, "Diamond", "White gem"}

   myMap := map[string]Item{
      "Ruby":    ruby,
      "Diamond": diamond,
   }
   fmt.Println(myMap)

   myMap["Ruby"] = Item{3, "This was a ruby", "gem"}
   myMap["NoRuby"] = Item{26, "Not a ruby", "Unkown gem"}
   fmt.Println(myMap)

   value, exists := myMap["Diamond"]
   fmt.Println("The value key Diamond exists in myMap?", exists)
   if exists {
      fmt.Println("The key Diamond has a value of", value)
   }

   value2, exists2 := myMap["NonExisting"]
   fmt.Println("The value key NonExisting exists in myMap?", exists2)
   if exists {
      fmt.Println("The key NonExisting has a value of", value2)
   }

}
$ go run gitlab.com/hectormartinez/map
map[Ruby:{0 Ruby Red gem} Diamond:{1 Diamond White gem}]
map[Ruby:{3 This was a ruby gem} Diamond:{1 Diamond White gem} NoRuby:{26 Not a ruby Unkown gem}]
The value key Diamond exists in myMap? true
The key Diamond has a value of {1 Diamond White gem}
The value key NonExisting exists in myMap? false
The key NonExisting has a value of {0  }

Here we reuse the Item type from our previous example to create a map of items with two keys: Diamond and Ruby. We can modify keys from a map simply by replacing the object in a certain key, or add a key placing an object in a new key. In Python, we can use key in dictionary to check if a key exists, here in Go we should try to access it and then check the proper variable (exists and exists2 in this example).

Functions

And now we move to functions. They are another basic topic of computing languages, as they reuse code. They process input and return an output, maybe performing some side effect (as printing or writing to a file). Another snippet!:

package main

import (
   "fmt"
   "strings"
)

func add(x int, y int) int {
   return x + y
}

func contains(str, substr string) (result bool) {
   result = strings.ContainsAny(str, substr)
   return
}

func main() {
   mynum := add(1, 1)
   bytes, err := fmt.Println(mynum)

   fmt.Println(bytes)
   fmt.Println(err)

   result := contains("MyString", "g")
   fmt.Println(result)
}
$ go run gitlab.com/hectormartinez/function
2
2
<nil>
true

Functions start with func, a name, the input, the output and the body. Here, we defined a func named add which takes x (which is an int),

We defined the function add, that takes x and y, both integers, and return another integer, which is the sum of both x and y. Then we save its result calling it with two ones, and pass the result to the fmt.Println function. That function return two things, an integer with the number of bytes written and an error.

Classes

Go does not have classes (from Go Tour). What?! Yeah, that's all, section finished.

package main

func main() {

}

Methods

However, you can define methods on types (from Go Tour too). Better now. So say we want to have a Path type, which can be joined with others, as pathlib.Path in Python.

package main

import (
   "fmt"
   "path/filepath"
)

// Path contain items
type Path string

func (path Path) join(other Path) (finalPath Path) {
   finalPath = path + Path(string(filepath.Separator)) + other
   return finalPath
}

func main() {
   usr := Path("/usr")
   lib := Path("lib")

   fmt.Println(usr)
   fmt.Println(lib)

   fmt.Println(usr.join(lib))

}
$ go run gitlab.com/hectormartinez/class
/usr
lib
/usr/lib

Here we import another package other than "fmt", which is "path/filepath" that gives utilities for paths management. Then we implement the type Path which comes from a string. Then we define a function and as a first element, we put (path Path), so Go knows that is a method of Path. In that element, we define the name of the "class" inside the body function, which will be path.

Summary

Here we took a fast view of the basic and more complex types and structures of Go. We learned how to assign variables and built that complex structures. Also, we closed with the definition of functions and methods. In the following articles about Go, we will be taking a look at interfaces, testing and the standard library... Stay tuned!

programming language programming basics