Post

Golang - Introduction

Some Go Facts

  • Created by Google in 2011
  • Language created from scratch
  • Open source
  • It is not a Google’s product
  • It is multiplatform
  • It is multi-purpose

Why Go?

  • Easy to code
    • Developers do not have a lot of freedom to write code the way they want
    • Easy to learn
    • Easy to read
  • Efficient compilation
  • Efficient execution

Characteristics

  • it has a strong and static type system
  • it has a C-inspired syntax
  • it is compiled
  • it is multi-paradigm (OOP, FP, Imperative)
  • it has garbage collection (automatic memory management) - variables which are not needed anymore are automatically deleted from memory
  • It is fast
  • It implements single binary compilation
  • It has a built-in concurrency support

Fundamentals of the language

  • We use .go files
  • code blocks are defined by curly braces { }
  • no styling freedom
  • we use ; at the end of each line to separate statements
  • it is case sensitive
  • it is strongly typed
  • it is not an object-oriented language
  • it has no classes and no exeptions
  • we have one file acttng as the entry point of the program
  • a file is a package
  • packages can have simple names(services) or urls (github.com/username/project)
  • wihin one go file we can have:
    • variables
    • functions
    • type declarations
    • method declarations

Modules and CLI

  • Modules are a collection of packages
    • It is our project
    • It contains a go.mod file with configuration and metadata
  • CLI manipulates the module
    • go mod init
    • go build
    • go run
    • go test
    • go get

Variables

  • In go, we declare variables using the var keyword followed by the name of the variable and the type of the variable.
1
var name string
  • variables have nil as default value (null in other languages)
  • we can create variables with initialisation
1
2
3
4
5
6
7
var name string
name = "John"

// or

otherName := "John" // this is called short declaration and works only inside functions

OR

1
const name string = "John"
  • constants in go are a bit different than in other languages
  • they do not occupy memory in most cases and are fixed values
  • they are mostly used for things that will not change after compilation
  • they MUST be set before compilation
  • we MUST NOT set the type of the variable immediately, because go will infer the type of the variable based on the value we assign to it
1
2
var name = "John" // this is valid and will be inferred as string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main(){
	var message = "Hello from Go!"
	var price = 10.5

// we need to print the variables to avoid linting errors if we are not using the variables immediately
	print(message, price)
}

// this is also valid.  Notice the `:=` operator

func main(){
	message := "Hello from Go!"
  price := 10.5


	print(message, price)
}

Built-in Data Types

  • string
  • Integer values include:

    • int (alias for int32)
    • int8
    • int16
    • int32
    • int64
    • uint (unsigned int and positive numbers only including 0)
    • uint8 etc…
  • Float values include:

    • float32
    • float64 (default)
  • bool (true or false)

Boolean operators:

  • && (and)
  • || (or)
  • ! (not)
  • ==(equal)
  • != (not equal)
  • < (less than)
  • > (greater than)
  • Pointers (memory address of a value) like a variable that points to another variable

Packages

  • We define the name of the package at the top of the file
  • We can import different packages in our file
  • Imports are done per file and not per package
  • Packages can either be standard built-in packages or custom packages
  • We an use the same package name in different files. The only condition is that they must be in the same folder
  • When we create a package, we do not need to import it in the file where we want to use it
1
2
3
// functions.go
package main

  • Having at least one go file with package main is mandatory which should have a function main
1
2
3
4
5
6
// main.go
package main

func main(){
  // code
}
  • print as a function is just a quick way to debug.
  • It is not recommended to use it in production code
    • it may not work on all platforms or operating systems
  • We can use the fmt package to print to the console

    1
    2
    3
    4
    5
    6
    7
    
    package main
    
    import "fmt"
    
    func main(){
      fmt.Println("Hello from Go!")
    }
    

Functions

  • We can define functions in go using the func keyword
  • There are public and private functions
    • Public functions start with a capital letter - Title case
    • Private functions start with a lowercase letter
    • It is like using the public and private keywords in other languages

Numbers

  • We can easily convert between numbers by using a global function with the type name
1
2
3
4
5
6
7
8
9
id := 5
price := 10.5

// convert int to float
floatPrice := float64(id)

// convert float to int
intPrice := int(price)

Strings

  • Multiline strings are created using backticks
1
2
3
4
message := `This is a multiline string
and this is the second line
and this is the third line
`

Collections

  • In go, there are Arrays, Slices and Maps
    • Arrays are fixed length collections of items of the same type .
      • We can not add or remove items from arrays
      • We can not change the length of arrays
      • We create an array by specifying the length of the array and the type of the items [5]int.
  • Slices are dynamic length collections of items of the same type (chunks of arrays) []int.
  • Maps are key-value pairs of items of the same type map[keyType]valueType.
  • We can get the length of Arrays and Slices by using the len() function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Arrays
var names [5]string
names[0] = "John"
names[1] = "Doe"
names[2] = "Jane"
names[3] = "Doe"
names[4] = "Mary"

Prices := [5]float64{10.5, 20.5, 30.5, 40.5, 50.5}


// Slices
names := []string{"John", "Doe", "Jane", "Doe", "Mary"}
names := append(names, "Peter")
println(len(names))

// Maps
prices := map[string]float64{"apple": 10.5, "orange": 20.5, "banana": 30.5}
wellKnownPorts := map[string]int {"http": 80, "https": 443}
  • Functions can receive arguments.
  • We can define the type of the arguments
1
2
3
func printMessage(message string){
  println(message)
}
  • If the function returns a value, we need to define the type of the return value after the arguments and before the curly braces
1
2
3
func add(x int, y int) int{
  return x + y
}
  • Functions can return multiple values
1
2
3
func addAndSubtract(x int, y int) (int, int){
  return x + y, x - y
}
1
2
3
4
5
func getFullname() (string, string){
  return "John", "Doe"
}

firstName, lastName := getFullname()
  • Functions can return named values
1
2
3
4
5
6
7
func getFullname() (firstName string, lastName string){
  firstName = "John"
  lastName = "Doe"
  return
}

firstName, lastName := getFullname()
  • we need to receive pointers instead of the value
1
2
3
4
5
6
7
  func addOne(x int){
    x++
  }

  func addOne(x *int){
    *x++
  }
  • We can use the _ to ignore a return value
1
2
3
4
5
6
7
func getFullname() (firstName string, lastName string){
  firstName = "John"
  lastName = "Doe"
  return
}

firstName, _ := getFullname()

Pointers and References

  • We can use the * operator to get the value of a memory address
  • We can use the & operator to get the memory address of a variable
1
2
3
4
5
6
7
8
9
10
func birthday(age *int){
  *age++
}

func main(){
  age := 10
  birthday(&age)
  fmt.Println(age)
}

Panic and defer - Curiousities of Function Execution

  • Panic is a function that stops the execution of the program
  • It can be compared to the throw keyword in other languages
  • Defer is a function that is executed at the end of the function
  • It can be compared to the finally keyword in other languages
1
2
3
4
5
func main(){
  defer fmt.Println("This is executed at the end of the function")
  panic("Something bad happened")
  fmt.Println("This is not executed")
}

Control Flow - Control Structures

  • In go we have the following control structures:
    • if
    • else
    • else if
    • switch(reloaded!)
    • for
  • there is no while or do while in go
  • No paranthesis are need around the condition
  • Only one type of equality operator ==
  • other operators &&, ||, !, <, >, <=, >=

  • Here is an example of a simple if-else statement
1
2
3
4
5
6
7
8
func main(){
  age := 10
  if age >= 18 {
    fmt.Println("You can vote!")
  } else {
    fmt.Println("You can not vote!")
  }
}
  • If statements can also be used to initialise variables which will be available only in the scope of the if statement
1
2
3
4
5
6
7
func main(){
  if age := 10; age >= 18 {
    fmt.Println("You can vote!")
  } else {
    fmt.Println("You can not vote!")
  }
}

Switch Statements

  • Switch statements are similar to if statements
  • They are used to compare a value against multiple conditions
  • We can use the fallthrough keyword to execute the next case even if the condition is not met
1
2
3
4
5
6
7
8
9
10
switch day {
  case "Monday":
    fmt.Println("Today is Monday! ✨")
  case "Saturday":
    fallthrough
  case "Sunday":
    fmt.Println("It' is the weekend! πŸŽ‰")
  default:
    fmt.Println("It's a weekday! πŸ‘Œ")
}
  • we can remove the condition from the switch statement and use it as a replacement for if-else statements
1
2
3
4
5
6
7
8
switch {
  case age >= 18:
    fmt.Println("You can vote!")
  case age < 18:
    fmt.Println("You can not vote!")
  default:
    fmt.Println("You have no age!")
}
  • In this case, the first condition that is met will be executed and the rest will be ignored
  • if no condition is met, the default case will be executed

for loops

  • We can use the for loop to iterate over collections
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Classic for
for i:=0; i<len(collection); i++{
  fmt.Println(collection[i])
}

// For range, similar to "for in" in JavaScript
for index := range collection{
  fmt.Println(collection[index])
}

// For range with index and value similar to "foreach" in JavaScript
for index, value := range collection{
  fmt.Println(index, value)
}
  • for can be used with a condition only
1
2
3
for condition {
  // code
}
  • a for loop with no condition will run forever
1
2
3
for {
  // code
}

Structures

  • Structures are similar to classes in other languages
  • It’s a data type with strongly typed properties
  • They have a default constructor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Person struct {
  firstName string
  lastName string
  age int
}

func main(){
  var john Person
  john = Person{
    firstName: "John",
    lastName: "Doe",
    age: 10,
  }
  peter := Person{"Peter", "Smith", 20}
}
  • You can add methods to it

Methods

  • Methods are functions that are attached to a structure
  • We define methods using the func keyword followed by the name of the method, the name of the structure and the type of the structure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Person struct {
  firstName string
  lastName string
  age int
}

func (p Person) GetFullName() string {
  return p.firstName + " " + p.lastName
}

func main(){
  var john Person
  john = Person{
    firstName: "John",
    lastName: "Doe",
    age: 10,
  }
  peter := Person{"Peter", "Smith", 20}
  fmt.Println(john.GetFullName())
  fmt.Println(peter.GetFullName())
}

Factories

  • Factories are functions that return a structure
  • They are used to create structures with default values
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Person struct {
  firstName string
  lastName string
  age int
}

func NewPerson(firstName string, lastName string, age int) Person {
  return Person{
    firstName: firstName,
    lastName: lastName,
    age: age,
  }
}

func main(){
  john := NewPerson("John", "Doe", 10)
  peter := NewPerson("Peter", "Smith", 20)
  fmt.Println(john.GetFullName())
  fmt.Println(peter.GetFullName())
}
  • In order to print factories, we need to implement the Stringer interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
type Person struct {
  firstName string
  lastName string
  age int
}

// Stringer interface
func (p Person) String() string {
  return fmt.Sprintf("%v %v is %v years old", p.firstName, p.lastName, p.age)
}

func NewPerson(firstName string, lastName string, age int) Person {
  return Person{
    firstName: firstName,
    lastName: lastName,
    age: age,
  }
}

func main(){
  john := NewPerson("John", "Doe", 10)
  peter := NewPerson("Peter", "Smith", 20)
  fmt.Println(john)
  fmt.Println(peter)
}

// Output
John Doe is 10 years old
Peter Smith is 20 years old

Embedding

  • Embedding is similar to inheritance in other languages
  • We can embed a structure inside another structure
  • The embedded structure will have all the properties and methods of the parent structure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Person struct {
  firstName string
  lastName string
  age int
}

type Employee struct {
  Person
  jobTitle string
  salary float64
}

func main(){
  john := Employee{
    Person: Person{
      firstName: "John",
      lastName: "Doe",
      age: 10,
    },
    jobTitle: "Software Engineer",
    salary: 1000,
  }
  fmt.Println(john.GetFullName())
}

Interfaces

  • An interface is a collection of methods
  • we can emulate polymorphism using interfaces
  • we can create an interface and then calling them on different structures
  • in order for a structure to implement an interface, it must implement all the methods of the interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

type Animal interface {
  Says() string
  NumberOfLegs() int
}

type Dog struct {
  Name string
  Breed string
}

type Gorilla struct {
  Name string
  Color string
}

func (d *Dog) Says() string {
  return "Woof"
}

func (d *Dog) NumberOfLegs() int {
  return 4
}

func (g *Gorilla) Says() string {
  return "Ugh"
}

func (g *Gorilla) NumberOfLegs() int {
  return 2
}

func main() {
dog := Dog{"Samson", "German Shepherd"}
gorilla := Gorilla{"King Kong", "Black"}

printInfo(&dog)
printInfo(&gorilla)
}


func PrintInfo(a Animal) {
  fmt.Println("This animal says", a.Says(), "and has", a.NumberOfLegs(), "legs")
}

// Output
This animal says Woof and has 4 legs
This animal says Ugh and has 2 legs

Goroutines and Channels

  • A goroutine is the Go way of using threads
  • We open a goroutine by using the go keyword followed by the function we want to run in the goroutine
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
	"fmt"
	"time"
)

func printMessage(text string) {
	for i := 0; i < 5; i++ {
		fmt.Println(text)
		time.Sleep(800 * time.Millisecond)
	}
}

func main() {
	go printMessage("Hello")
	printMessage("My Name is John!")
}
  • We can use channels to communicate between goroutines
  • A channel is a special type of variable that can be used to send and receive data between goroutinesd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
	"fmt"
	"time"
)

func printMessage(text string, channel chan string) {
	for i := 0; i < 5; i++ {
		fmt.Println(text)
		time.Sleep(800 * time.Millisecond)
	}
  channel <- "Done"
}

func main() {
  var channel chan string
	go printMessage("Hello", channel)
response := <- channel
  fmt.Println(response)
}

Wait Groups

  • Wait groups are used to wait for goroutines to finish

Testing

  • We can create tests for our code
  • Testing is built-in in go
  • A test file is a file with the name of the file we want to test and the suffix _test.go
  • We can create tests by using the testing package
  • We define tests using the func keyword followed by the name of the function we want to test and the *testing.T type
  • The functions call methods of T.
  • We can create subtests using goroutines
  • We can use the CLI to run tests with go test
  • Testing in go use the TableDrivenTests pattern
  • From Go 1.19, Fuzzing is built-in in go
  • Fuzzing is a technique used to find bugs in our code by generating random data and passing it to our functions
1
2
3
4
5
6
7
8
9
// name_test.go

package api_test

import "testing"

func TestAPICall(t *testing.T) {

}

Go Templates

  • they are HTML files with embedded go code
  • it is in the html/template package
  • we can use the { { } } to embed go code
  • We can trim spaces using the - operator
  • We can use Actions and Pipelines
  • We can use the { { range } } action to iterate over collections
  • We can use { { if } } and { { else } } to create conditions

Compiling

  • To compile a go program, we use the go build . command
  • This will create an executable file with the name of the folder
  • We can specify the output folder by using the -o flag go build . -o build/
  • We can compile for different platforms by using the GOOS and GOARCH environment variables env GOOS=target-OS GOARCH=target-architecture go build .
  • We can compile and install a go program by using the go install . command

Packaging

  • Go just produces a binary file
  • It does not provide a packaging solution
  • We can use Docker to package our go programs
  • For windows we need to create installers
  • For Linux we can use deb or rpm packages
  • For Mac we can use dmg packages
This post is licensed under CC BY 4.0 by the author.