An Introduction to Golang

An Introduction to Golang

Golang also known as Go is an open-source, compiled and statically typed programming language developed by Google.

Go is a compiled, concurrent, statically-typed general purpose programming language with simple syntax and a robust standard libraries. Go improves upon concepts like imperative and object-oriented programming and thereby simplifies the working environment for developers.

In this guide, you will learn everything you need to know to get started using Go to build real-world applications.

Why learn Golang?

Now you still might be asking why should I learn Golang with all the other alternatives out there. Here is a list of some of the cons of the Go programming language.

  • Simple syntax - Go has concise and straightforward syntax that makes writing readable and maintainable code easy.
  • Compiled language
  • Static linking - The compiler supports static linking which means you can statically link your project into one massive binary and then simply deploy it to the cloud or a server.
  • Open source - Go is open-source so you can read the source code and contribute to the repository.

Now that you have a sense of what Golang is and what it brings to the table let's jump into the installation and basics.

Installation

Go can be installed on all three major platforms Windows, Linux and Mac.

Windows:

Download the latest Windows Golang MSI and execute it on your machine. Follow the instruction prompts to install Go in your root directory and automatically set up an environment variable for your terminal.

Linux:

On Linux you can download the tar file from the official website and unzip it to /usr/local.

Mac:

On Mac you can download the Mac installer from the official website and execute it using a double tap.

Verifying the installation:

You can verify the installation by running the following command in your terminal.

go --version

After installing, you will continue by taking a look at the basic syntax and features Golang provides.

Basic structure

Let's start by taking a look at the basic structure of a Golang program. For that, we are going to take a look at a hello world example.

package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}

All Go programs must be part of a package, and here you use main to make the file executable. The main package requires a main function, which will be called when running the file.

You must import all the packages you use including all the standard libraries like fmt in this case.

Use the following command in your terminal to run the file:

go run main.go

Variables

In this section, you will learn the different ways to declare and initialize variables.

Golang is statically typed, which means that variables either explicitly or implicitly assign a type before your program runs.

Declaring variables:

Variables can be declared using the var keyword followed by the variable name and the datatype.

var i int
var s string

Initializing variables:

After declaring the variables they can be initialized using the = operator.

i = 20
s = "Some String"

It is equally valid to declare and initialize the variable in a single line.

var k int = 35

You can also omit the type when initializing the variable at the time of declaration.

var score = 10

Short variable declaration:

The var keyword can also be omitted using the := short variable declarator.

j := 50
str := "Some String!"

Declaring multiple variables:

It is also possible to declare multiple variables in a single line of code.

firstName, lastName := "FirstName", "LastName"

Variable Declaration Block:

Variable declarations can also be grouped together for better readability and cleaner code.

var (
    name = "Donald Duck"
    age  = 50
)

Constants

A constant is a variable with a fixed value that cannot change under any circumstances.

Declaring a constant:

Constants are declared by using the const keyword instead of var. You must assign the value at the time of the declaration because the value will fix it after that.

const PI float64 = 3.14159265359
const VALUE = 1000

Note: The names of constant variables are usually written in uppercase.

Declaring using a declaration block:

Constants can also be declared using declaration blocks for better readability.

const (
	PRODUCT  = "Ice Cream"
	QUANTITY = 50
)

Datatypes

As already mentioned Golang is a statically typed programming language, which means that variables always have a type that cannot change.

Here is a list of all the datatypes available in Golang:

uint8       unsigned  8-bit integers (0 to 255)
uint16      unsigned 16-bit integers (0 to 65535)
uint32      unsigned 32-bit integers (0 to 4294967295)
uint64      unsigned 64-bit integers (0 to 18446744073709551615)
int8        signed  8-bit integers (-128 to 127)
int16       signed 16-bit integers (-32768 to 32767)
int32       signed 32-bit integers (-2147483648 to 2147483647)
int64       signed 64-bit integers (-9223372036854775808 to 9223372036854775807)

float32     IEEE-754 32-bit floating-point numbers
float64     IEEE-754 64-bit floating-point numbers
complex64   complex numbers with float32 real and imaginary parts
complex128  complex numbers with float64 real and imaginary parts

byte        alias for uint8
rune        alias for int32

uint     unsigned, either 32 or 64 bits
int      signed, either 32 or 64 bits
uintptr  unsigned integer large enough to store the uninterpreted bits of a pointer value

You can set the data type of a variable after the variable name.

// Integer
var i int = 5
fmt.Println(i)

// String
var s string = "Hello World!"
fmt.Println(s)

// Float
var f float64 = 3.14159265359
fmt.Println(f)

// Boolean
var b bool = true
fmt.Println(b)

Converting Datatypes

Converting also known as casting datatypes, is an important concept when needing a precise type is expected. This section will show you the most common type conversions.

String to float:

One of the most common conversions is a string to a float. For this, you use the strconv package, which is the standard package for converting strings to basic datatypes.

s := "3.1415926535"
f, err := strconv.ParseFloat(s, 8)

if err != nil {
    fmt.Println("Error convert string to integer", err)
}

fmt.Println(reflect.TypeOf(f))

Float to string:

Converting a float to a string is a bit easier because their cannot be an error.

var flo float64 = 3.1415926535
var strFlo string = strconv.FormatFloat(flo, 'E', -1, 32)
fmt.Println(reflect.TypeOf(strFlo))

Float to int:

Casting of basic datatypes like int and floats is a lot easier and can be done using the following syntax.

var f32 float32 = 3.1415926535
fmt.Println(reflect.TypeOf(f32))

i32 := int32(f32)
fmt.Println(reflect.TypeOf(i32))

Operators

An operator is a symbol that tells the Go compiler to perform a specific action. The following code blocks will show you all the basic operators of the Go programming language and how you can use them in your applications.

Arithmetic Operators:

Arithmetic operators are used to perform common arithmetic actions like adding or subtracting numbers. Here is a list of the most common arithmetic operators:

  • '+' (Addition) - Adds to operands
  • '-' (Subtraction) - Builds the sum of two numbers by subtracting them
  • '*' (Multiplication) - Multiplies two values with each other
  • '/' (Division) - Divides two values
  • '%' (Modulus) - Gets the remainder after an integer division

Let's look at how you can actually use these operators in your applications.

package main

import "fmt"

func main() {
    var i int = 10
    var k int = 20

    // Arithmetic Operators
    fmt.Printf("i + k = %d\n", i+k)
    fmt.Printf("i - k = %d\n", i-k)
    fmt.Printf("i * k = %d\n", i*k)
    fmt.Printf("i / k = %d\n", i/k)
    fmt.Printf("i mod k = %d\n", i%k)
}

The output of running the above program should be:

# Output
i + k = 30
i - k = -10
i * k = 200
i / k = 0
i mod k = 10

Comparison Operators:

Comparison operators are used to compare two values with each other.

  • '==' (Equal to) - Returns true if x is equal to y
  • '!=' (Not equal to) - Returns true if x is not equal y
  • '<' (Lesser than) - Returns true if x is lesser than y
  • '<=' (Lesser than Equal to) - Returns true if x is lesser than or equal to y
  • '>' (Greater than) - Returns true if x is greater than y
  • '>=' (Greater than Equal to) - Returns true if x is greater than or equal to y

Here is an example of these operators in action.

package main

import "fmt"

func main() {
    var i int = 10
    var k int = 20

    // Comparison Operators
    fmt.Println(i == k)
    fmt.Println(i != k)
    fmt.Println(i < k)
    fmt.Println(i <= k)
    fmt.Println(i > k)
    fmt.Println(i >= k)
}

You should see the following output when running the program:

# Output
false
true
true
true
false
false

Logical Operators:

Logical operators are used to combine multiple conditions or to complement the evaluation of the original condition.

  • '&&' (Logical AND) - Returns true if all conditions are true
  • '||' (Logical OR) -  Returns true if at least one condition is true
  • '!' (Logical NOT) -  Inverts the result of the condition e.g. true to false and vise versa

Here is an example to show you the operators in action:

package main

import "fmt"

func main() {
    var i int = 10
    var k int = 20
    var z int = 30

    // Logical Operators
    fmt.Println(i < z && i > k)
    fmt.Println(i < z || i > k)
    fmt.Println(!(i == z && i > k))
}

You should see the following output when running the program:

# Output
false
true
true

Assignment Operators:

Assignment operators are used to assign a specific value to a variable.

  • '=' (Simple Assignment) -  Assigns the value to the variable
  • '+=' (Add Assignment) -  Adds the value to the current value of the variable
  • '-=' (Subtract Assignment) -  Subtracts the value to the current value of the variable
  • '*=' (Multiply Assignment) -  Multiplies the current value of the variable with the new value
  • '/=' (Division Assignment) - Divides the current value of the variable by the new value
  • '%=' (Modulus Assignment) - Takes modulus of the two values and assigns the result to left operand

Here is a practical example of the assignment operators:

package main

import "fmt"

func main() {
    // Assignment Operators
    var x, y = 15, 25
    x = y
    fmt.Println("= ", x)

    x = 15
    x += y
    fmt.Println("+=", x)

    x = 50
    x -= y
    fmt.Println("-=", x)

    x = 2
    x *= y
    fmt.Println("*=", x)

    x = 100
    x /= y
    fmt.Println("/=", x)

    x = 40
    x %= y
    fmt.Println("%=", x)
}

You should see the following output when running the program:

# Output
=  25
+= 40
-= 25
*= 50
/= 4
%= 15

Functions

Functions are code blocks that help you divide your program into smaller packs of code that are reusable and easily readable. In this section, you will learn everything you need to know to start writing functions in Golang including declaration, parameters, return values and many more.

Declaration:

Functions can be declared using the func keyword followed by the function name, a pair of parentheses and a code block containing the functionality.

The function can then be called using the function name followed by parentheses. Here is an example of a basic hello world function that takes no parameters and returns no value.

package main

import "fmt"

func main() {
	helloWorld()
}

func helloWorld() {
	fmt.Println("Hello World!")
}

Parameters:

Sometimes you will need to pass information to your function, which can be done using parameters. Arguments are specified after the first pair of parentheses and are basically just variables.

package main

import "fmt"

func main() {
	hello("Go")
	add(20, 30)
}

func hello(x string) {
	fmt.Printf("Hello %s\n", x)
}

func add(x int, y int) {
	fmt.Println(x + y)
}

You should see the following output when running the program:

Hello Go
50

Note: You can pass as many arguments as you want. But it is good practice to move everything higher than 4 or 5 into some kind of object or struct for better readability.

Return values:

Returning values from a function is another essential concept and can be done by providing a return type after the first pair of parentheses. You can then use the return statement, as shown in the following example.

package main

import "fmt"

// Returning a single value of type int
func add(x int, y int) int {
	return x + y
}

func main() {
	// Accepting return value in variable
	sum := add(20, 30)
	fmt.Println("Sum: ", sum)
}

Named return value:

You can also define a function with a named return type that has the advantage of not providing the variable name in the return statement.

package main

import "fmt"

// Named return value
func getArea(l int, b int) (area int) {
	area = l * b
	return // Return without specify variable name
}


func main() {
	// Accepting a named return value
	area := getArea(10, 10)
	fmt.Println("Area: ", area)
}

Return multiple values:

Golang also allows you to return multiple parameters, which can help in numerous practical scenarios like error handling while returning a second variable.

package main

import "fmt"

// Returning multiple name values
func rectangle(l int, b int) (area int, parameter int) {
	parameter = 2 * (l + b)
	area = l * b
	return
}

func main() {
	// Accepting multiple return values
	area, parameter := rectangle(10, 10)
	fmt.Println("Area: ", area)
	fmt.Println("Parameter", parameter)
}

Passing addresses to function:

It is also possible to pass the address of a variable to a function and then modify them using the reference inside your function.

package main

import "fmt"

// Passing addresses to a function
func addValue(x *int, y *string) {
	*x = *x + 5
	*y = *y + " World!"
	return
}

func main() {
	var number = 20
	var text = "Hello"
	fmt.Println("Before:", text, number)

	addValue(&number, &text)

	fmt.Println("After:", text, number)
}

Anonymous functions:

An anonymous function is a function that doesn't contain any name, which can be useful if you want to create an inline function.

package main 
  
import "fmt"
  
func main() { 
  func(name string) {
		fmt.Println("Hello ", name)
  }("Everyone!")
} 

Assigning function to a variable:

Anonymous functions allow you to assign a function to a variable, as shown in the example below.

package main

import "fmt"

// Defining a anonymous function
var (
	area = func(l int, b int) int {
		return l * b
	}
)

func main() {
	area := area(10, 10)
	fmt.Println(area)
}

Closure functions:

Closure functions are a special case of an anonymous function where you access outside variables.

package main

import "fmt"

func main() {
	l := 10
	b := 10

	// Closure functions are a special case of a anonymous function where you access outside variables
	func() {
		var area int
		area = l * b
		fmt.Println(area)
	}()
}

Variadic functions:

A variadic function is a special kind of function that can take an arbitrary number of arguments.

package main

import (
	"fmt"
	"reflect"
)

func main() {
	printMultipleStrings("Hello", "World", "!")
}

// Passing multiple atributes using a variadic function
func printMultipleStrings(s ...string) {
	for i := 0; i < len(s); i++ {
		fmt.Println(s[i])
	}
}

Variadic functions also allow you to pass in multiple different variable types using an empty interface.

package main

import (
	"fmt"
	"reflect"
)

func main() {
	printMultipleVariables(1, "green", false, 1.314, []string{"foo", "bar", "baz"})
}

// Pass multiple different datatypes
func printMultipleVariables(i ...interface{}) {
	for _, v := range i {
		fmt.Println(v, "--", reflect.ValueOf(v).Kind())
	}
}

Deferred function calls:

Defer is a unique Go statement that schedules a function call to be run after the function completes. Consider the following example:

package main

import "fmt"

func first() {
	fmt.Println("First Function")
}

func second() {
	fmt.Println("Second Function")
}

// Defer is a spezial statement that schedules functions to be executed after the function completes
func main() {
	// The Second function will be called after the first
	defer second()
	first()
}

After running the code you should see that the first method got executed before the second even though the second was called first.

# Output
First Function
Second Function

Higher-order functions:

Higher-order functions are functions that either receive a function as an argument or return a function as its return value. Consider the following example:

package main

import "fmt"

func multiply(x, y int) int {
	return x * y
}

// Function that returns another function
func partialMultiplication(x int) func(int) int {
	return func(y int) int {
		return multiply(x, y)
	}
}

func main() {
	multiple := partialMultiplication(10)
	fmt.Println(multiple(10))
}

Control Structures

Control structures are used to specify whether a block of code should be executed or not. In this section, you will learn about control structures and how you can use them in your applications.

If-Else:

If statements are used only to execute the code if the specified condition evaluates to true.

package main

import (
	"fmt"
)

func main() {
	// If Statement
	x := true
	if x == true {
		fmt.Println("True")
	}
}

The if statement can be followed up by an else statement, which will be executed when the if statement evaluates to false.

package main

import (
	"fmt"
)

func main() {
	// If-Else Statement
	y := 100
	if y > 80 {
		fmt.Println("Greater than 80")
	} else {
		fmt.Println("Lesser than 80")
	}
}

If statements can also be chained using else if statements as shown in the following example.

package main

import (
	"fmt"
)

func main() {
	// If-Elseif Statement
	grade := 5
	if grade == 1 {
		fmt.Println("You have an A")
	} else if grade > 1 && grade < 5 {
		fmt.Println("You have no A but you are positiv")
	} else {
		fmt.Println("Your grade is negativ")
	}
}

The if statement in Golang can also contain a short variable declaration preceding the conditional expression.

package main

import (
	"fmt"
)

func main() {
	// If statement initialization
	if a := 10; a == 10 {
		fmt.Println(a)
	}
}

Switch:

A switch statement takes a specified expression and compares it against a list of multiple cases. Once the case matches the expression, it will execute the code block.

package main

import (
	"fmt"
)

func main() {
	// Switch Statement
	num := 1
	switch num {
	case 1:
		fmt.Println("One")
	case 2:
		fmt.Println("Two")
	default:
		fmt.Println("Many")
	}
}

Multiple cases can also execute a single code block, as shown in the following code block.

package main

import (
	"fmt"
)

func main() {
	// Switch Statement with multiple cases
	switch num {
	case 1, 2, 3, 4, 5:
		fmt.Println("Some")
	case 6, 7, 8, 9:
		fmt.Println("More")
	default:
		fmt.Println("Many")
	}
}

Fallthrough keyword:

The fallthrough keyword can be used to force the execution flow to fall through the successive case block

package main

import (
	"fmt"
)

func main() {
	// Switch fallthrough case statement (forces the execution of the next statement)
	dayOfWeek := 3
	switch dayOfWeek {
	case 1:
		fmt.Println("Go to work")
		fallthrough
	case 2:
		fmt.Println("Buy some bread")
		fallthrough
	case 3:
		fmt.Println("Visit a friend")
		fallthrough
	case 4:
		fmt.Println("Buy some food")
		fallthrough
	case 5:
		fmt.Println("See your family")
	default:
		fmt.Println("No information available for that day.")
	}
}

That means if case one is executed it will automatically also execute the following case. You should see the following output after running the program.

# Output
Visit a friend
Buy some food
See your family

Switch conditional statements:

Switch also allows you to use conditional statements in your cases.

package main

import (
	"fmt"
)

func main() {
	// Switch using conditional statements
	switch {
	case num < 5:
		fmt.Println("Smaller than 5")
	case num == 5:
		fmt.Println("Five")
	case num > 5:
		fmt.Println("Bigger than five")
	default:
		fmt.Println("No information about the number")
	}
}

Switch initializer:

The switch statement also allows you to initialize the variable directly after the switch keyword, which will only be accessible inside the statement.

package main

import (
	"fmt"
)

func main() {
	// Switch initializer statement
	switch number := 5; {
	case number < 5:
		fmt.Println("Smaller than 5")
	case number == 5:
		fmt.Println("Five")
	case number > 5:
		fmt.Println("Bigger than five")
	default:
		fmt.Println("No information about the number")
	}
}

Loops

Loops are used to iterate over sequences or execute a code block a certain amount of times. Golang contains only a single loop type, which is the for loop. The for loop also replaces the functionality of a while loop in other languages.

For loop:

The most basic use case of a for loop is when you already know how often you want to loop through beforehand. Here is an example of looping from 0 to 9 and printing out the output.

package main

import (
	"fmt"
)

func main() {
	// Basic for loop
	for i := 0; i <= 10; i++ {
		fmt.Println(i)
	}
}

You can also define an infinite loop and stop it using the break statement inside the loop, as shown in this example.

package main

import (
	"fmt"
)

func main() {
	// Infinite loop
	i := 0
	for {
		fmt.Println("Hello World!")
		// Breaks/Stops the infinite loop
		if i == 10 {
			break
		}
		i++
	}
}

The range keyword is used to iterate over expressions that evaluate to an array, slice, map or string.

package main

import (
	"fmt"
)

func main() {
	// Ranges
	strings := []string{"Hello", "World", "!"}

	// Get the index and value while looping through the range
	for i, val := range strings {
		fmt.Printf("%d: %s \n", i, val)
	}

	// Only getting the index
	for i := range strings {
		fmt.Println(i)
	}

	// Only getting the value
	for _, val := range strings {
		fmt.Println(val)
	}
}

While loop:

As stated above, a for loop can also be used as a while loop. This loop is executed as long as the condition is true.

package main

import "fmt"

func main() {
	// Basic while loop
	x := 0
	for x < 10 {
		fmt.Println(x)
		x++
	}
}

A do-while loop can be achieved by using a condition and the break statement inside the for loop.

package main

import "fmt"

func main() {
	// Do while loop
	num := 0
	for {
		// Work
		fmt.Println(num)

		if num == 10 {
			break
		}
		num++
	}
}

Arrays

An array is a collection of elements of a single type. It is used to hold multiple values at the same time. These values are referred to as the array elements and can be accessed using a specific index. Arrays are fixed in size and therefore cannot grow or shrink.

Declaring Arrays:

To declare an array you first need to specify the number of elements followed by the data type of the elements the array holds.

package main

import "fmt"

func main() {
	// Declaring an Array
	var intArray [5]int
}

This example creates an integer array with a length of five.

Assigning values and accessing elements:

Values can be accessed and assigned by using the index number, which will be specified in square brackets.

package main

import "fmt"

func main() {
	// Declaring an Array
	var intArray [5]int

	// Assigning values
	intArray[0] = 10
	intArray[1] = 2

	// Accessing the elements
	fmt.Println(intArray[0])
	fmt.Println(intArray[1])
}

Initializing an Array using Array literals:

Arrays can also be initialized with predefined values using array literals. For that, you specify the number of elements, the data type of the elements followed by the predefined values wrapped in curly braces.

package main

import "fmt"

func main() {
	// Initialize Array using Array literals
	x := [5]int{0, 5, 10, 15, 20}
	var y [5]int = [5]int{0, 5, 10, 15, 20}

	fmt.Println(x)
	fmt.Println(y)
}

You can also initialize elements for specific values using the following syntax.

package main

import "fmt"

func main() {
	// Initialize values for specific array elements
	a := [5]int{1: 1, 4: 25}

	fmt.Println(a)
}

Initializing an Array with ellipses:

The ellipse syntax can be used to automatically detect the size of the array based on the elements specified in the array declaration.

package main

import "fmt"

func main() {
	// Initializing an Array with ellipses
	k := [...]int{10, 20, 30}

	fmt.Println(len(k))
}

Copying an array:

Copying an array can be done using two ways. You can either copy all its values or copy the reference of the array.

  • Copy values - The copied array has the same values but is independent of the original array
  • Copy reference - Changes to the original array will be reflected in the copied array and vise versa

Here is an example for better understanding.

package main

import "fmt"

func main() {
	x := [5]int{0, 5, 10, 15, 20}

	// Copy array values
	y := x

	// Copy by reference
	z := &x

	fmt.Printf("x: %v\n", x)
	fmt.Printf("y: %v\n", y)

	x[0] = 1

	fmt.Printf("x: %v\n", x)
	fmt.Printf("y: %v\n", y)
	fmt.Printf("z: %v\n", *z)
}

In this example, we copy the array x into two new arrays and then change an item in the original array. The array copying the reference of the original will also change the value will the array copying the values will stay the same.

Iterating over an array:

Looping through array elements can be done using every kind of loop. Here are a few examples.

package main

import "fmt"

func main() {
	x := [5]int{0, 5, 10, 15, 20}

	// Standard for loop
	for i := 0; i < len(x); i++ {
		fmt.Println(x[i])
	}

	// Getting index and value using range
	for index, element := range x {
		fmt.Println(index, "=>", element)
	}

	// Only getting value using range
	for _, value := range x {
		fmt.Println(value)
	}

	// Range and counter
	j := 0
	for range x {
		fmt.Println(x[j])
		j++
	}
}

Slices

Slices are dynamic arrays that can grow and shrink as you see fit. Like arrays, slices also use indexable and have a length.

Creating a slice:

Slices can be created using multiple techniques:

  • Basic slice definition by emitting the length in the square brackets
  • Creating a slice using the build-in make() function, which takes the datatype, length and capacity as a parameter
  • Initializing the slice using a slice literal
  • Creating a slice using the new keyword

Here is an example for all listed techniques:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// Create an empty slice
	var x []int
	fmt.Println(reflect.ValueOf(x).Kind())

	// Creating a slice using the make function
	var y = make([]string, 10, 20)
	fmt.Printf("y \tLen: %v \tCap: %v\n", len(y), cap(y))

	// Initialize the slice with values using a slice literal
	var z = []int{10, 20, 30, 40}
	fmt.Printf("z \tLen: %v \tCap: %v\n", len(z), cap(z))
	fmt.Println(z)

	// Creating a Slice using the new keyword
	var a = new([50]int)[0:10]
	fmt.Printf("a \tLen: %v \tCap: %v\n", len(a), cap(a))
	fmt.Println(a)
}

Adding items:

Items can be added to a slice using the append() method, which takes a slice and a value as an argument.

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// Add items using the append function
	var b = make([]int, 1, 10)
	fmt.Println(b)
	b = append(b, 20)
	fmt.Println(b)
}

If the space of the underlying slice is sufficient the value is saved in the slice. If not a new slice is created to store the value.

Accessing items:

As arrays, slices also use indices to access values.

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// Access slice items
	var c = []int{10, 20, 30, 40}
	fmt.Println(c[0])
	fmt.Println(c[0:3])
}

Changing item values:

Values can be changed by referring to a specific index and setting a new value using the = operator.

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// Change item values
	var d = []int{10, 20, 30, 40}
	fmt.Println(d)
	d[1] = 35
	fmt.Println(d)
}

Copying and appending items:

Slices can be copied using the build-in copy() function, which copies the data of one slice to another. You can also append all items of a slice to another using the append() function and a spread operator.

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// Copy slice into another slice
	var e = []int{10, 20, 30, 40}
	var f = []int{50, 60, 70, 80}
	copy(e, f)
	fmt.Println("E: ", e)

	// Append a slice to an existing one
	var g = []int{10, 20, 30, 40}
	var h = []int{50, 60, 70, 80}

	g = append(g, h...)
	fmt.Println(g)
}

Maps

A map is an unordered collection that allows you to store key/value pairs comparable to a HashMap in Java or a Python dictionary. The value can then quickly be retrieved using the key, which acts as an array index.

Declaration:

There are multiple techniques for declaring a map. Here is a small list of the most common methods:

  • Declaring an empty map and then adding objects
  • Initializing the map using some default values
  • Declaring the map using the make function

Here is an example of all these techniques:

package main

import "fmt"

func main() {
	// Declaring empty map
	var shopingList = map[string]int{}
	fmt.Println(shopingList)

	// Initializing a map
	var people = map[string]int{"Elon": 10, "Jeff": 15}
	fmt.Println(people)

	// Map declaration using make function
	var peopleList = make(map[string]int)
	peopleList["Elon"] = 10
	peopleList["Jeff"] = 15
	fmt.Println(peopleList)
}

Accessing and manipulating map items:

Accessing and manipulating a map is quite similar to an array. Items can be accessed by providing the key in squared brackets.
Adding new items is done by providing a new key to the map and assigning a value to the variable. Updating works the same, but instead of a new key you can use the already existing key you want to update.
Items can be deleted using the delete function and passing the map and the key as function arguments. The function will then delete the item in the map instance itself and therefore not have a return value.

package main

import "fmt"

var m = map[string]string{
	"c": "Cyan",
	"y": "Yellow",
	"m": "Magenta",
	"k": "Black",
}

func main() {
	// Accessing items
	fmt.Println(m["c"])

	// Adding items
	m["b"] = "black"
	fmt.Println(m)

	// Updating items
	m["y"] = "lemon yellow"
	fmt.Println(m)

	// Deleting items
	delete(m, "b")
	fmt.Println(m)
}

Iterating over a map:

The for loop using the range keyword can be used to quickly iterate over a map and get both the key and value variables. Keep in mind that a map is an unordered collection and there will be no possibility to predict in which order the items will be returned.

package main

import "fmt"

var m = map[string]string{
	"c": "Cyan",
	"y": "Yellow",
	"m": "Magenta",
	"k": "Black",
}

func main() {
	// Iterating over a map
	for k, v := range m {
		fmt.Printf("Key: %s, Value: %s", k, v)
	}
}

Checking if an item exists:

You can also check if an item exists inside the map using the second returned value when fetching an item from the map. The value is a boolean and will return false if the map does not contain the value you searched for.

package main

import "fmt"

var m = map[string]string{
	"c": "Cyan",
	"y": "Yellow",
	"m": "Magenta",
	"k": "Black",
}

func main() {
	// Test if an item exists
	c, ok := m["y"]
	fmt.Println("\nc: ", c)
	fmt.Println("ok: ", ok)
}

Struct

Structs are a collection of data fields with predefined types and allow you to define your own data type similar to classes in Java. Each of the data fields of a struct is declared using a predefined type (Either build-in or user-defined).

Structs improve the modularity of your application and help you define the structure of complex objects, which can be passed to functions or used in other ways.

Declaring a struct:

Declaring a struct is done by using both the type statement before the actual name and the struct statement after the name. Then you can define the fields and datatypes of every field inside the object.

package main

import "fmt"

// Declaring a Struct
type Animal struct {
	name   string
	weight int
}

func main() {
}

Here we declare a struct with the name ,,Animal" which has two fields - name of type string and weight of type int.

Creating a struct:

After declaring a struct there are multiple ways of creating an instance. Here is a list of the most common creating processes:

  • Declaring a variable with the struct as the datatype without initializing it
  • Creating an instance using struct literate
  • Creating an instance using the new keyword
  • Using the pointer address operator to create a  new instance

Here are examples showcasing these methods:

package main

import "fmt"

// Declaring a Struct
type Animal struct {
	name   string
	weight int
}

func main() {
	// Creating an instance of a struct
	var dog Animal
	dog.name = "Dog"
	dog.weight = 40
	fmt.Println(dog)

	// Creating an instance using struct literate
	var cat = Animal{name: "Cat", weight: 5}
	fmt.Println(cat)

	// Creating an instance using the new keyword
	var bird = new(Animal)
	bird.name = "Bird"
	bird.weight = 1
	fmt.Println(bird)

	// Creating an instance using the pointer address operator
	var monkey = &Animal{name: "Monkey", weight: 10}
	fmt.Println(monkey)
}

Comparing struct instances:

In Go the == operator or the reflect.DeepEqual() method can be used to compare two instances of the same struct with each other. Both will first check if the two objects are an instance of the same struct and then continue by comparing the values of the fields. If all values are the same true will be returned.

package main

import "fmt"

// Declaring a Struct
type Animal struct {
	name   string
	weight int
}

func main() {
	// Creating an instance
	var bird = new(Animal)
	bird.name = "Bird"
	bird.weight = 1
	fmt.Println(bird)

	var monkey = &Animal{name: "Monkey", weight: 10}
	fmt.Println(monkey)

	// Comparing struct instances
	fmt.Println(bird == monkey)
    
    // Comparing struct instances using DeepEqual
    fmt.Println(reflect.DeepEqual(bird, monkey)) 
}

Copying a struct:

Copying an instance of a struct is important when you want a second instance with the same values that do not reference the original one. That means that when you are changing one of the instances the other one will not be affected.

package main

import "fmt"

// Declaring a Struct
type Animal struct {
	name   string
	weight int
}

func main() {
	// Creating an instance using the pointer address operator
	var monkey = &Animal{name: "Monkey", weight: 10}
	fmt.Println(monkey)

	// Copying struct type using pointer reference
	monkey2 := monkey
	monkey2.name = "Monkey2"
	fmt.Println(monkey2)
}

Struct methods:

Methods can be associated with a struct by defining the associated type inside of the head of the method. In the following example the ConfInfo() function is associated with the Config struct and can be used by every instance of the Config struct.

package main

import "fmt"

type Config struct {
	Env   string
	Proxy ProxyInfo
}

type ProxyInfo struct {
	Address string
	Port    string
}

func (conf Config) ConfInfo() string {
	fmt.Println("Env: ", conf.Env)
	fmt.Println("Proxy: ", conf.Proxy)
	return "----------------------"
}

func main() {
	c := &Config{
		Env: "DEBUG:TRUE",
		Proxy: ProxyInfo{
			Address: "addr",
			Port:    "port",
		},
	}

	fmt.Println(c.ConfInfo())
}

Nested struct:

Structs can also be nested inside of each other by giving the field of a struct the type of another struct. This allows you to create complex data formats that can easily be reused.

package main

import "fmt"

// Nested Struct
type Configuration struct {
	Env   string
	Proxy Proxy
}

type Proxy struct {
	Address string
	Port    string
}

func main() {
	// Creating an instance of a nested struct
	c := &Configuration{
		Env: "DEBUG:TRUE",
		Proxy: Proxy{
			Address: "addr",
			Port:    "port",
		},
	}
	fmt.Println(c)
	fmt.Println(c.Proxy.Address)
}

Here we use the Proxy struct inside the Configuration struct as a datatype.

JSON field tags:

When declaring a struct you can also specify optional field tags (JSON or YAML for example). These will help you when formatting an instance of the struct into a specific format and vise versa.

package main

import (
	"encoding/json"
	"fmt"
)

type Dog struct {
	Name   string `json:"name"`
	Weight string `json:"weight"`
}

func main() {
	json_string := `
    {
        "name": "Rocky",
        "weight": "45"
    }`

	rocky := new(Dog)
	json.Unmarshal([]byte(json_string), rocky)
	fmt.Println(rocky)

	spot := new(Dog)
	spot.Name = "Spot"
	spot.Weight = "20"
	jsonStr, _ := json.Marshal(spot)
	fmt.Printf("%s\n", jsonStr)
}

The example above shows you how you can use this for converting a JSON string into an instance of the Dog struct and how you can convert an instance of the Dog struct back into JSON.

Interfaces

An interface is Golang is a type that defines a set of methods signatures. A type then satisfies the interface if it implements all the method signatures provided by the interface.

Defining an Interface:

You can define an interface using the interface keyword after your type name as shown below.

package main

import (
	"fmt"
)

// Defining a interface
type User interface {
	PrintName(name string)
}

type Vehicle interface {
	Alert() string
}

func main() {

}

The User interface defines a single method signature called PrintName(). Any type that implements the PrintName() function will therefore now satisfy the User interface.

Implementing the Interface:

When implementing an interface in your struct you will not need to explicitly specify the interface that is implemented. Go will automatically determine if the struct implements the interface by checking if it implements all the method signatures defined in the interface.

package main

import (
	"fmt"
)

// Defining a interface
type User interface {
	PrintName(name string)
}

type Vehicle interface {
	Alert() string
}

// Create type for interface
type Usr int

type Car struct{}

// Implement interface function in type
func (usr Usr) PrintName(name string) {
	fmt.Println("User Id:\t", usr)
	fmt.Println("User Name:\t", name)
}

func (c Car) Alert() string {
	return "Hup! Hup!"
}

func main() {
	var user1 User
	user1 = Usr(1)
	user1.PrintName("Gabriel")

	c := Car{}
	fmt.Println(c.Alert())

	Print(20)
}

Defining an empty interface:

There is also a way to define an empty interface which is often used for functions that accept any datatype. It is also often combined with the ... spread operator which allows for any number of parameters to be passed.

package main

import (
	"fmt"
)

// Define an empty interface - Often used for functions that accepts any type
func Print(a ...interface{}) (n int, err error) {
	return fmt.Println(a...)
}

func main() {
	
}

The Print() method in this example accepts any type of argument because of the empty interface and also allows the user to pass infinite parameters because of the spread operator.

Further steps

You should now be familiar with the basics of the Go programming language, but there are still a lot of concepts to learn and you will need practice for the already learned skills.

I recommend starting to practice by building real world projects. You can find a collection of all the code and a lot of real-world Go projects in the following Github repository, which should help you on the way to becoming a better Go developer.

I can also recommend the Manning Go in Action book, which I personally read to get up to speed on Golang (Disclaimer: Affiliate link).

Sources

Here are the sources used for writing this article:

Conclusion

You made it all the way until the end! I hope that this article helped you understand the basics of the Go programming language and how you can use it in your applications.

If you have found this useful, please consider recommending and sharing it with other fellow developers and subscribing to my newsletter. If you have any questions or feedback, let me know using my contact form or contact me on Twitter.

Read these next: