The Only Introduction to Go (Golang) You Need
Go, or also called Golang, is absolutely trendy, and rightly so. It is not as difficult to learn as C or C++, but still quite fast, and has a great community & many interesting and helpful packages and libraries. The language was also developed by some of the brightest minds in the computer science world at Google.
These are probably enough reasons to look at the language in which Docker and Kubernetes were written. Here is the ultimate Golang cheatsheet you need to get started. Have fun!
Table of Contents ๐
- Hello World
- Variables
- Getting User Input
- Data Types
- Functions
- Conditionals (if-else)
- Loops
- Arrays
- Pointers
- Splitting up Code
- Custom Types & Receiver Functions
- Maps
Hello World in Go ๐
Letโs start with the absolute basics. If you already have Golang installed, you can type go version
into your terminal and you should see a useful output. If you havenโt installed it yet, you can do that on the Go official webiste
.
Letโs start with Hello World. For this, we just create a file called main.go
, fill it with the following content, and then we can run our first program with go run main.go
. You should see an output in the terminal.
package main
import "fmt"
func main(){
fmt.Println("Hello, world!")
}
fmt
stands for formatting and is the default library for input and output in Go. As you can see, we have a main function in the file, it is executed automatically without us having to call it. Every Go project needs such a function.
These are the essential instructions we need. If we want to compile our project and make it into an executable file, we use go build main.go
.
go run main.go
go build main.go
./main
Variables in Golang ๐
Some things are really special about Go. One thing is, there is no null
that describes the value of a variable that has not yet been assigned a value. Instead, each variable type has a default value, depending on the data type of the variable.
If we declare a variable without a value, we must specify a datatype, otherwise, variable types are optional.
var name string
var age int
var adult bool
var height float32
func main(){
fmt.Println(name) // ""
fmt.Println(age) // 0
fmt.Println(adult) // false
fmt.Println(height) // 0
}
There are two ways to define variables: the classic way, using the variable-keyword, and the shorthand syntax. Above we used the keyword, not the shorthand.
As you have seen, we specified the data type. With the shorthand syntax, we canโt do that, besides, we can use the long syntax to create an empty variable, with the shorthand syntax we have to assign a value directly.
func main(){
name := "Max"
fmt.Println(name) // "Max"
}
The shorthand syntax for declaring a variable does not work outside of a function. Letโs look at the other possibility again.
var name = "Max"
func main(){
fmt.Println(name) // "Max"
}
Go uses type-inference to set the data type, based on the value we save inside of the variable, whenever we donโt provide a data type. As you can see it is optional for the long syntax to specify a data type.
var name string = "Max"
We can also easily define several variables in one go.
var age, grade int = 15, 9
func main(){
fmt.Println(age) // 15
fmt.Println(grade) // 9
}
If we have already given a variable a value, we can overwrite it, this works for variables created with the var-keyword and the shorthand syntax (:=
).
name := "Max"
name = "Tom"
fmt.Println(name) // "Tom"
The exception is variables, which have the variable type const
. This stands for constant and means, that we cannot overwrite the value of the variable, we create a constant like this: const name = "Max"
.
Getting User Input ๐
What would a program be if the user could not make any input? In Go we can do this in two ways. We can accept input from the user via the terminal during code execution with the Scanln
function.
func main(){
fmt.Println("Enter something: ")
var first string
fmt.Scanln(&first)
fmt.Print("Input: " + first + "\n")
}
We store the input in the variable first and then output the input directly afterward. Another possibility is to pass parameters in the console directly when executing the program.
import (
"fmt"
"os"
)
func main(){
arg := os.Args[1]
fmt.Println(arg)
}
We need to import the os
package, which gives access to the terminal, then we can access os.args
, which contains everything we enter when we run the Go program.
We access the second element through os.Args[1]
, because the first element in the terminal is the go run main.go
command, with which we start our program.
Now we can start the program and pass command-line parameters like so: go run main.go name=max
.
Our program should output name=max
.
Golangโs Data Types ๐
In Go, there are four important data types that you should know, Integers, Floats, Strings & Booleans. Here is a simple overview.
Integers are whole numbers, floats are comma numbers, strings are โtexts,โ and booleans are truth values, thus true or false.
With the unsigned variables, the numerical value can never be negative. With signed variables, it can. Here is an example.
The following line of code will cause an error:var age uint8 = -2
, the compiler will say that -2 is not in the range of an unsigned integer, as said, unsigned means that it cannot be negative.
So we have to rewrite the code and say that it is a signed integer: var age int8 = -2
, now everything works, the u
stands for unsigned. If we do not specify a u
, the variables are signed. But what does the 8 mean?
The 8 indicates the size of the variable in the memory in bits, so we may have a variable of eight bits, which gets a value assigned, which is greater than eight bits. This then logically gives an error because the range is not sufficient.
This will cause an error: var age int8 = 235255
, the value is too large to be represented with eight bits.
Thanks to the number behind the int-expression, we can easily define how big our variable should be in memory. We can do the same with floats.
Functions ๐
Go is a compiled language, typically for compiled languages because you can declare a function after executing it. Yes, the following code will work absolutely fine.
func main(){
greet() // "Hello!"
}
func greet(){
fmt.Println("Hello!")
}
Return something from a function ๐
When defining variables, specifying data types is optional, but if we return something from a function, it is necessary to specify the data type. In the greet function, it is a string that we return.
func main(){
fmt.Println(greet()) // "Hello!"
}
func main() string {
return "Hello!"
}
Return multiple values ๐
This is another thing that is special in Go, we can return several things in one function. In most other languages, this is not possible. There, you have to return an array to get a similar effect.
func person() (string, int){
return "Max", 23
}
func main(){
fmt.Println(person()) // Max 23
name, age := person()
fmt.Println(name) // Max
fmt.Println(age) // 23
}
Since we can also define several variables at once with the shorthand syntax, we can use this cleanly with the multiple returns in Golang, it is very similar to destructuring in JavaScript.
Function Parameters ๐
When passing parameters, it is the same as with the return value, we have to specify the data type. The special thing is that we write the data type after the name of the parameter, not before it, as it is in most programming languages.
func double(number int) int {
return number * 2
}
func main(){
fmt.Println(double(2)) // 4
}
Just like we can abbreviate the declaration of variables, we can also abbreviate the definition of the parameters a bit. With the following syntax, we set the same data type for both parameters we expect.
func add(num1, num2 int) int {
return num1 + num2
}
func main(){
fmt.Println(add(2, 4)) // 6
}
Conditionals ๐
If-Else probably exists in every programming language. This is also true for Golang, and it works just like everywhere else.
In the syntax, you only notice that we do not use brackets around the condition itself.
func main(){
if 6 > 5 {
fmt.Println("6 > 5")
} else {
fmt.Println("5 > 5")
}
}
Of course, there is also the usual else-if available in Go. The keyword for it is else if
.
Loops in Golang ๐
The only real loop that exists in Golang is the for-loop
, all the more important that we have a look at it. But the syntax is more or less the same as in any other language. We donโt use brackets around the condition.
for i := 0; i < 10; i++ {
fmt.Println(i)
}
Now all numbers from zero to nine are displayed in the terminal.
We are not allowed to use the var-keyword to define a variable within the for-syntax. The only one way is to declare the counter variable directly, with the shorthand as shown above. i
is only valid within the for-loop; outside the curly brackets, we cannot access it.
A while-loop
alternative in Golang ๐
I just said that there is actually only the For-Loop
in Golang, and that is true, a While-Loop
with the while-keyword does not exist.
But you can build such a while loop relatively easy by using the for-loop
. We can simply leave out all unnecessary building blocks of the for-loop
, and what remains is a technical while-loop
.
for 6 > 5 {
fmt.Println("spam")
}
As long as the condition is true, the loop is executed.
Of course, nothing changes in the condition; 6 will always be greater than 5, so it is an infinite while-loop
, but we can write it easier.
for {
fmt.Println("spam")
}
The code will now run all the time, it works like while true
.
Arrays in Go ๐
Letโs have a look at arrays in Golang. It is important to understand that arrays have certain rules:
- They can only contain one type of data.
- They have a fixed size.
- The fixed size means we can not add more values to the array.
Maybe you know arrays from languages like JavaScript, where the upper conditions do not apply, but when we talk about arrays in Go, we mean classical arrays, which are also very undynamic in many other low languages.
So when we work with arrays, we have to consider exactly this, already, when we create one, we have to specify how many elements are stored in them.
Here is a simple array, which should have two elements, we store them directly when declaring.
var students = [2]string{"Max", "Anna"}
fmt.Println(students) // [Max Anna]
But we can also create an empty array first and pass the values to it during execution.
var students [2]string
students[0] = "Max"
students[1] = "Anna"
This way, we get the same result as in the code above, but because arrays always have a fixed length, we cannot add more elements than we have specified in size.
We can also create an array with the shorthand syntax. In Go, as in many other languages, we start counting at 0, so the first element has index 0. Unlike in Python, for example, the index cannot be negative, we cannot use -1 to select the last element of the array.
If we want to have an array to which we can add more values, we can omit the number in the square brackets, but then it is no longer an array but a slice.
Here we create a slice to which we add another element, but the append function does not overwrite the actual slice but returns a new one, so we overwrite the existing slice with it.
var students = []string{"Max", "Anna"}
students = append(students, "Karl")
fmt.Println(students) // [Max Anna Karl]
Combining Arrays / Slices and the for-Loop ๐
Itโs time to bring two concepts together., with for-Loops, we can loop through Arrays and Slices.
var students =[]string{"Max", "Anna"}
for i, value := range students {
fmt.Println(i, value)
}
i
is our index-variable, value
is the element at the corresponding position, a common problem is the following: Go likes to give an error message if we donโt use a variable, so simply not accessing the variable i
leads to an error.
If we just write for value
, then value will be i, because it is in the first place, so we have to skip the first place, and this is done as follows:
for _, value := range students {
fmt.Println(value)
}
Now only each element in our slice is output, but not the corresponding index.
Pointers in Go ๐
As the name already says, a pointer points to something, in programming languages, it normally points to a storage address or holds a storage address, which is behind a value.
var name = "Abanoub"
var namePointer = &name
fmt.Println(namePointer) // 0xc00008e1e0
If you execute this code yourself, you might get a different output, but it should be similar in for, depending on the program execution, the memory address, which the program can use to store variables, changes. By memory, we mean the RAM, not the harddisk.
If we have the memory address, we can read directly what is stored there, thus the value of a variable.
With &
we get the memory address, with *
the value behind.
var name = "Abanoub"
var namePointer = &name
fmt.Println(*namePointer) // Abanoub
Working With Multiple Files ๐
Writing everything into one file is not very useful, rather, we want to divide our code to have separate files for individual functions or classes. In Go, this is very, very convenient because we donโt even need some kind of import or export statement.
In a Golang project, we should only have one main.go
at a time, but all our files can use the same package, namely package main
.
Letโs first create a main.go
with the following content:
package main
func main(){
greet()
}
On execution, a function called greet is called directly, for it, we create the greet.go
with the following content.
package main
import "fmt"
func main(){
fmt.Println("Hello!")
}
You can see directly that we also import the fmt-package, which we didnโt do in main.go
, and thatโs completely correct, we only have to import an external package like fmt
in the file we use it in. Now we can run it.
Maybe you have already tried it on your own and noticed an error, but that is not because we forgot to import the greet function from greet.go
somehow.
We need to adjust the way the program runs. Instead of go run main.go
we need to run go run main.go greet.go
, we tell Go that two files are relevant for execution, and suddenly, it works!
Custom Types & Receiver Functions ๐
Letโs create a new slice that contains the names of the students of a course.
func main(){
var students = []string{"Abanoub", "Mark"}
fmt.Println(students) // [Abanoub Mark]
}
That was easy. But what if we want to create a separate slice for each of the other courses, which also contains strings, then we can only create an extra type for that.
type people []string
func main() {
var students = people{"Abanoub", "Mark"}
fmt.Println(students) // [Abanoub Mark]
}
The type serves as a template we create with the type-keyword. Then we can access it with the name.
It becomes a bit abstract, because we can create a so-called receiver
function, which can be accessed by all variables of type people
.
type people []string
func (p people) print() {
for _, card := range p {
fmt.Println("Student: " + card)
}
}
func main() {
var students = people{"Abanoub", "Mark"}
students.print()
}
Yes, the function above the main function looks a bit strange, basically, the function is called print
, but we have marked it as a receiver. Inside the brackets, it gets the type it should work with, p is the copy of our student slice, people
is the type for which the function should be available.
At the bottom of the main function, we can then call this function, it will produce the following output as we just go through the slice.
Student: Abanoub
Student: Mark
So p is inside the function print
of the students
slice. If you come from an object-oriented language, you can think of p as this, by the way, how to name p is not important, it can be chosen freely, but the second parameter (the type) must be clearly defined.
Golang Maps ๐
A problem when switching between different programming languages is often that the same concepts have different names, this is also the case with maps. Maps would be objects in JavaScript, dictionaries in Python. They assign a key to each value.
All keys must have the same data type, and all values too, but keys and values do not necessarily have to have the same datatype.
Here is an example.
1: "Abanoub"
2: "Mark"
So we might just have a numbered enumeration of students, sorted by their performance, 1 is the best, 2 the second-best, and so on, so we have different data types for the keys and the values. We can also make a map for the yearbook and assign a different key to each value (student).
Team Captain: Abanoub
Prom Queen: Anna
I think the principle is clear, letโs take a look at maps in practice.
For this, we use the map-keyword and specify first the data type of the key in the square brackets, then the data type of the value.
students := map[string]string {
"Prom Queen": "Anna",
"Team Captain": "Abanoub",
}
Here we used the shorthand syntax to declare a map, but we can also use the var-keyword, then the map can be empty by default.
If we create an empty map, as we do now, instead of using the var-keyword over another one, we can enter the values as follows.
students := make(map[string]string)
students["Team Captain"] = "Abanoub"
fmt.Println(students["Team Captain"]) // Abanoub
To output only one value from the map, we access the variable and specify the corresponding key.
To delete a single entry, we use the delete-function, in which we specify the map and the key: delete(students, "Team Captain")
.
These were the most important basics you should know about Go (Golang), in a few minutes.