Learning golang

Those are my personal notes on my journey to learn go and is not a full tutorial, i assume that you have already installed go on your…

Learning golang
golang

Those are my personal notes on my journey to learn go and is not a full tutorial, i assume that you have already installed go on your system.

The simplest valid go program

The simplest go program valid for compilation is this, save it as main.gopackage mainfunc main() {
}

go give us two options, one is to compile the program into an executable and the second one is to run it without compilation (to be honest i believe that still compilation occurs somehow in the background)

How to compile a program

In the following example we can see that compiling main.go file produces a binary file named mainkpatronas@narcissus:~/go/src/hello_world$ go build main.go
kpatronas@narcissus:~/go/src/hello_world$ ls
main  main.go
kpatronas@narcissus:~/go/src/hello_world$

If we run the executable will not produce anything on screen because our source file main.go had no any command that generate ouput on screen. lets try to print something, change the code to thispackage mainimport "fmt"func main() {
fmt.Println("Hello world")
}

Now if we build and re-run the executable would produce “Hello world” on screenkpatronas@narcissus:~/go/src/hello_world$ go build main.go
kpatronas@narcissus:~/go/src/hello_world$ ./main
Hello world

How to run a program without compilation

We can run the program without compilation using the go run commandkpatronas@narcissus:~/go/src/hello_world$ go run main.go
Hello world

Comments in go

go supports multi-line and single line comments

  • single line comments start with //
  • multi-line comments start with /* and end with */package mainimport "fmt"/*
    Multi
    Line
    Comment
    */// Single line commandfunc main() {
    fmt.Println("Hello world")
    }

Strings and characters in go

Simple strings in go are enclosed in double quotes

fmt.Println("Just a string")

Multi-line strings are enclosed over back-ticks and they can also contain “quotes”.

fmt.Println(`This 
is 
A 
"multi-line" 
string.`)

go supports unicode by design, this means that we can pass bytes that represent a unicode character and this character will printed on screenfmt.Println("\u2272")

We can also print the hex number of a character using single quotesfmt.Println('A')
65

Numbers in go

When doing calculations with integers go will never return a float.fmt.Println("Division:",20/3)
6

To return a float at least one of the numbers must be floatfmt.Println("Division:",20/3.0)
6.666666666666667

Booleans

Boolean operators are operators that return trueor falseon execution, go supports the following operatorsvar1 == var2 // equal
var1 != var2 // not equal
var1 <  var2 // less than
var1 >  var2 // greater than
var1 >= var2 // greater or equal than
var1 <= var2 // less or equal than

Note that comparing an interger and a float will return results as expected.

Variables

There are two ways to declare variables the verbose way and the short way, the short way ommits the var keyword and the type of the variable// The verbose way
var i int = 2
// The short way
j := 2

Using the verbose way we can declare numbers with an initial value of 0 and strings with zero lengthvar i int
var name string
fmt.Println("i",i)
fmt.Println("Name",name)

Executing this will outputi 0
Name

There is also the option to declare two or more variables in one linevar i,j,k int = 1,2,3
var l,m,n int
name, surname := "kostas","patronas"
_, whatever   := "sdsds","just a string"

The “_” is called blank identifier and is used to inform go that intentionally we are not using this variable.

Arrays and slices

Arrays in go are sequences of elements of the same type, arrays always have specified length.names := [3]string{"aaa","bbb","ccc"}

Values of array elements can be set after creationvar names [3]string
names[0] = "kostas"

Slices are variable length arrays and we can append elements dynamicallynames := []string{}
names = append(names,"kostas")
names = append(names,"nick")
fmt.Println(names)
[kostas nick]

If we know the initial number of elements for a slice we can predefine them, this can be done with two ways, the first way is to use append with multiple arguments.names := []string{}
names = append(names,"kostas","nikos")

The second way is to use the make function to preallocate the spacenames := make([]string, 4)
names[0] = "kostas"
names[1] = "nikos"

To get the number of elements of a slice or an array we use the len functionfmt.Println(len(names))
2

maps

Maps in go are what dictionaries are in Python, it is a non ordered sequence of elements that consist of a unique key that corresponds to a valueages := map[string]int{
   "kostas":39,
   "nikos": 40,
}
fmt.Println(ages)
map[kostas:39 nikos:40]

Maps have variable length and we can add, read and remove items from a map dynamically.

To get the value of a keyfmt.Println(ages["nikos"])
40

To add an itemages["pavlos"] = 25

To delete an itetm we will use the delete function, as arguments we need the name of the map and a key,delete(ages, "kostas")

Conditionals

The if statement allows us to add conditional logic to our programs, the if statement evaluates given expressions to boolean values, if the value is true the code inside the if block will be executed, if its false that code will not run.a := 1
if a == 1 {
     fmt.Println("This will run because a is equal to one")
}

This code will printThis will run because a is equal to one

There is also the else statement that compliments if and instructs go what to do in case that the if statement evaluates to falsea := 2
if a == 1 {
   fmt.Println("This will run because a is equal to one")
} else {
   fmt.Println("This will run because a is not equal to one")
}

This code will printThis will run because a is not equal to one

Also there is the else if statement which allows us to do more complex conditional logica := 2
 if a == 1 {
     fmt.Println("This will run because a is equal to one")
 } else if a == 2 {
     fmt.Println("This will run because a is equal to two")
 } else {
     fmt.Println("This will run because a is not equal to one or two")
 }

This code will printThis will run because a is equal to two

Having too many if statements can make our code very complex and hard to read, go provides the switch statement that can make our code easier to read and maintain. The above code can be writen with switcha := 2
switch {
case a == 1:
   fmt.Println("This will run because a is equal to one")
case a == 2:
   fmt.Println("This will run because a is equal to two")
default:
   fmt.Println("This will run because a is not equal to one or two")
}

This code will printThis will run because a is equal to two

The default statement will executed if no case statement evaluates to true, default can be ommited if there is no need to exist.

One thing you need to know about the switch behavior is that switch will not evaluate all cases, instead will stop evaluating as soon as a single case statement is true.a := 10if a > 1 {
   fmt.Println("if a is greater than 1")}if a > 2 {
   fmt.Println("if a is greater than 2")}switch {
  case a > 1:
      fmt.Println("switch a is greater than 1")
  case a > 2:
      fmt.Println("switch a is greater than 2")}

As we can see both if statements executed because both evaluate to true, but using switch only the first case statement executed despite both cases can evaluate to trueif a is greater than 1
if a is greater than 2
switch a is greater than 1

loops

Go supports only one loop type, the “for” statement, for syntax is like thisfor var := init_statement; condition; post_statement

On a real world example this translates to something like thisfor i:=1;i<=10;i++ {
//code
}

  • init_statement: runs once
  • condition: must be true to execute the code inside the for block
  • post_statement: runs after each iteration and usually is used to modify the variable created in init_statement and evaluated to the condition statement

Example: simple for loopfor i:=1;i<=3;i++ {
  fmt.Println(i)
}

Output:1
2
3

Example: looping an array

To loop an array or a map we need the range functionmy_array := []int{1,2,3}for index,value := range my_array {
   fmt.Printf("index: %d value: %d\n",index,value)}

Output:index: 0 value: 1
index: 1 value: 2
index: 2 value: 3

Example: looping a mapmy_map := map[string]int{}
   my_map["kostas"] = 39
   my_map["nikos"] = 40for key,value := range my_map {
   fmt.Printf("key: %s value: %d\n",key,value)}

Output:key: kostas value: 39
key: nikos value: 40

Example: emulating the while statement

Not all arguments are required for the for statement, this allows us to be flexible and emulate the while functionality, in this example if a is equal to 5 then the for will exit the loopa:=0
for a <= 5 {
   fmt.Println(a)
   a++
}

Functions

Functions are many commands grouped together, this allows us to re-use code without the need to re-write the code, functions might or might not get arguments and return results

Example: a function that greets someone (concat two strings)

Function hello_world gets a name which is a string, when is called it returns name concated with “Hello “package main
import "fmt"func main() {
   h := hello_world("kostas")
   fmt.Println(h)
}func hello_world(name string) string {
   return fmt.Sprintf("Hello %s",name)
}

Example: a function that does not accept or returns anythingpackage main
import "fmt"func main() {
   hello()
}func hello() {
   fmt.Printf("Hello!\n")
}

Reading user input

To read user input from the keyboard (stdin) we need to create a bufio.NewReader struct that will bind to os.Stdinreader := bufio.NewReader(os.Stdin)

Then we will use the Reader.ReadString function to read from the structinput_data, _ := reader.ReadString('\n')

And finally the strings.TrimSpace function will remove the trailing space from Stdininput_data = strings.TrimSpace(input_data)

Example: read user inputpackage main
import "fmt"
import "bufio"
import "os"
import "strings"func main() {
   reader := bufio.NewReader(os.Stdin)
   fmt.Print("Input: ")
   input_data, _ := reader.ReadString('\n')
   input_data = strings.TrimSpace(input_data)
   fmt.Println(input_data)
}

Running this will ask for “Input” and then will print input to the stringInput: hello
hello

Working with files

Writing a file

The following example does the following

  1. Opens file data.txt using the following parameters
  • os.OpenFile tries to open file data.txt
  • os.O_CREATE instructs go to create the file if does not exist
  • os.O_WRONLY opens the file in write only mode
  • os.O_APPEND appends data to be written to the end of the file, if we ommit this all previous data from the file will be lost
  • 0644 are the permissions for the file

2. Then checks if there is an error while opening the file, if not execution continues, else the program exits

3. file.Write will write the data variable to the file as plain bytes

4. And finally we will check if write returned any errors

5. The defer statement is executed when the function (in this case main()) returns, in our case when main() function ends go will close file data.txtimport "fmt"
import "os"
.
.
.
data := "My message\n"file, err := os.OpenFile("data.txt", os.O_APPEND | os.O_CREATE | os.O_WRONLY, 0664)if err != nil {
   fmt.Println("Error: Unable to open data.txt")
   os.Exit(1)}defer file.Close()
_,err = file.Write([]byte(data))if err != nil {
   fmt.Println("Error: failed to write to data.txt")
   os.Exit(1)}

Reading a file

The following example does the following

  1. Opens file data.txt using the following parameters
  • os.OpenFile tries to open file data.txt
  • os.O_RDONLY opens the file in read only mode
  • 0644 are the permissions for the file

2. Then checks if there is an error while opening the file, if not execution continues, else the program exits

3. bufio.NewScanner(file) creates a new scanner variable that will use to read the file line by line

4. for scanner.Scan() loop reads the file line by line as unicode and prints data to the screen

5. The defer statement is executed when the function (in this case main()) returns, in our case when main() function ends go will close file data.txtimport "fmt"
import "os"
import "bufio"
.
.
.
file, err := os.OpenFile("data.txt",os.O_RDONLY, 0664)if err != nil {
  fmt.Println("Error: Unable to open data.txt")
  os.Exit(1)}defer file.Close()
scanner := bufio.NewScanner(file)for scanner.Scan(){
   fmt.Println(scanner.Text())}

Creating a tool with command line arguments

We will create a simple tool that accepts command line parameters to do some complex things, the tool can accept the following set of arguments

  1. filename,data: giving filename and data will write those data to the file
  2. filename: giving only filename will print the file contents to the screen

To parse the command line arguments we use the flag package which provides command line agruments parsing capabilities

First we need to declare the variables that will be used to store the arguments, then we use the flag.StringVar which defines a string argument

its syntax isfunc StringVar(variable, "argument name", "default value", "usage")

Then we parse the arguments using the flag.Parse() functionvar filename string
var data stringflag.StringVar(&filename, "filename", "", "File To Read or Write")
flag.StringVar(&data, "data", "", "Data To Be Written")
flag.Parse()

Here you can find the full example of the tool, i will not explain the code since is the combination of the previous examples to a single program.package mainimport "fmt"
import "os"
import "bufio"
import "flag"func main() {var filename string
var data stringflag.StringVar(&filename, "filename", "", "File To Read or Write")
flag.StringVar(&data, "data", "", "Data To Be Written")
flag.Parse()if filename == "" {
  flag.Usage()
  os.Exit(1)}if data == "" {
   file, err := os.OpenFile(filename, os.O_RDONLY, 0664)
   if err != nil {
       fmt.Println("Error: Unable To Open ", filename)
       os.Exit(1)}defer file.Close()
   scanner := bufio.NewScanner(file)
   for scanner.Scan() {
       fmt.Println(scanner.Text())}
   } else {
   file, err :=     os.OpenFile(filename,os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664)
   if err != nil {
       fmt.Println("Error: Unable To Open ", filename)
       os.Exit(1)}
   defer file.Close()data = fmt.Sprintf("%s\n", data)
    _, err = file.Write([]byte(data))if err != nil {
       fmt.Println("Error: Failed To Write To ", filename)
       os.Exit(1)}
   }
}

Cross-Platform compilation

Go allows us to compile the program for many platforms

Example: compile for LinuxGOOS=linux GOARCH=amd64 go build -o example -i example.go

Example: compile for WindowsGOOS=windows GOARCH=amd64 go build -o example.exe -i example.go

Example: compile for MacGOOS=darwin GOARCH=amd64 go build -o example -i example.go

Epilog

I hope this small tutorial gave you an easy start to your journey on learning Go :)

Join Medium with my referral link - Konstantinos Patronas
As a Medium member, a portion of your membership fee goes to writers you read, and you get full access to every story…