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.
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 pros of the Go programming language.
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.
Go can be installed on all three major platforms Windows, Linux and Mac.
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.
On Linux you can download the tar file from the official website and unzip it to /usr/local.
On Mac you can download the Mac installer from the official website and execute it using a double tap.
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.
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
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.
Variables can be declared using the var keyword followed by the variable name and the datatype.
var i int
var s string
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
The var keyword can also be omitted using the := short variable declarator.
j := 50
str := "Some String!"
It is also possible to declare multiple variables in a single line of code.
firstName, lastName := "FirstName", "LastName"
Variable declarations can also be grouped together for better readability and cleaner code.
var (
name = "Donald Duck"
age = 50
)
A constant is a variable with a fixed value that cannot change under any circumstances.
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.
Constants can also be declared using declaration blocks for better readability.
const (
PRODUCT = "Ice Cream"
QUANTITY = 50
)
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 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.
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))
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))
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))
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 are used to perform common arithmetic actions like adding or subtracting numbers. Here is a list of the most common arithmetic operators:
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 are used to compare two values with each other.
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 are used to combine multiple conditions or to complement the evaluation of the original condition.
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 are used to assign a specific value to a variable.
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 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.
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!")
}
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.
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)
}
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 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)
}()
}
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())
}
}
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 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 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 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)
}
}
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 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.
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)
}
}
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++
}
}
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.
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.
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])
}
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)
}
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 can be done using two ways. You can either copy all its values or copy the reference of the array.
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.
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 are dynamic arrays that can grow and shrink as you see fit. Like arrays, slices also use indexable and have a length.
Slices can be created using multiple techniques:
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)
}
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.
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])
}
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)
}
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)
}
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.
There are multiple techniques for declaring a map. Here is a small list of the most common methods:
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 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)
}
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)
}
}
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)
}
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 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.
After declaring a struct there are multiple ways of creating an instance. Here is a list of the most common creating processes:
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)
}
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 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)
}
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())
}
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.
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.
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.
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.
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)
}
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.
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).
Here are the sources used for writing this article:
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.