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 thename
of the variable and thetype
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 notper 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 console1 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
andprivate
keywords in other languages
- Public functions start with a
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
.
- Arrays are fixed length collections of items of the same type .
- 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
flaggo build . -o build/
- We can compile for different platforms by using the
GOOS
andGOARCH
environment variablesenv 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
orrpm
packages - For Mac we can use
dmg
packages
This post is licensed under CC BY 4.0 by the author.