Go Fundamentals: A Practical Approach to Data Types and Structures

John O. EmmanuelJohn O. Emmanuel
14 min read

Go, commonly referred to as Golang, is a statically typed programming language designed to simplify the creation of reliable and efficient software. This series on Golang aims to demystify the language, enabling readers to get started and develop high-quality software quickly. This first article will take a practical approach to understanding some of its fundamental concepts.

Prerequisites

Before you begin this tutorial you'll need the following:

  • Computer

  • Prior programming knowledge

  • Go 1.x installed on your machine

  • A Go Playground or Code Editor (Vscode)

Program Template

package main

import ("fmt")

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

The above program is a typical Go code that you’ll get used to seeing in every Go file you work with. The output will be “Hello, World!”

package is a reserved keyword in Golang, it helps to keep related code together in one place. In the code above, package main means all the rest of the code in this file belongs to the ‘main package’. When a Go program is run, it looks for the function named main and runs that first and that’s why we named this function main.

import (“fmt“) says we’ll be using text-formatting code from the “fmt“ package**.** Go files or programs always have one or more import statement. Each program or file needs to import other packages before its code can use the code other packages contain. In addition, to load all the Go code on our computer would cause our program to be very slow, so we can instead import only the packages we need.

func main() {} is a special function named main, it gets run first when we run our program. In Go files or programs, there can only be one main function in the entire codebase, think of this as a room that has an entrance via the door; that single door is likened to this main function we’re talking about here. Also, main is a reserved keyword in Go.

fmt.Printlin(“Hello, World!“) is calling the “Printlin“ function from the “fmt“ package to display “Hello, World!“ in our terminal or console. “Println“ simply means “Printing and break to the next line“, it can take one or more arguments separated by commas. For instance,

fmt.Println ("Hello,", "World!")

Data Types and Variables

The Go basics types are String, Runes, Booleans, and Numbers (int,float64, float32, complex64, complex128).

String

A string is a series of bytes that usually represent text characters. We can define strings directly within our code using string literals, that is text between double quotation marks that Go will treat as string. Below are the examples:

"Hello, Gophers!" // The output is Hello, Gophers!
"Hello, \nGophers!" 
/* Because of the escape sequence, the output will be:
Hello,
Gophers!
*/
"Hello, \tGophers!" // The output is Hello,  Gophers!
"Code: \"\"" // The output is Code: ""
Escape SequenceValue
\nA newline character
\tA tab character
\”Double quotation marks
\A backlash

Runes

In Go, runes are used to represent single characters. unlike String literals which are written surrounded by double quotation marks (““), rune literals are written with single quotation marks (‘‘). Go uses the Unicode standard for storing runes and they are kept as numeric codes, not the characters themselves. Let’s take a look at the examples below:

fmt.Println('A') // The output is 65
fmt.Println('B') // The output is 66
fmt.Println('\t') // The output is 9
fmt.Println('\n') // The output is 10
fmt.Println('\\') // The output is 92
fmt.Println('*') // The output is 1174

Boolean

In Golang, the Boolean type is written as bool. Boolean values can either be true or false. They are useful with conditional statements, which cause a section of code to run only if a condition is true or false. Let’s take a look at the examples below:

3 == 3 // true
5 > 6 // false
10 < 20 // true
3 == 3 && 5 > 6 // false
3 == 3 || 5 > 6 // true
5 > 6 && 10 < 20 // false
5 > 6 || 10 < 20 // true

Numbers

In Golang, a number type can be int (int8 int16 int32 int64), unit (uint8 uint16 uint32 uint64 uintptr), float32, float64, complex64, complex128 . The int, uint, and uintptr types are usually 32 bits wide on 32-bit systems and 64 bits wide on 64-bit systems. When you need an integer value you should use int unless you have a specific reason to use a sized or unsigned integer type. Likewise, when you need a decimal value, you should use float64. Numbers are used for mathematical operations using arithmetic operators.

Variables

Just like in any programming language, a variable is a piece of storage containing a value. A variable can be named by using a variable declaration. To declare a variable in Go, var keyword followed by the desired name and the type of value the variable will hold. For example,

var age int // This variable can only hold an integer value
var height, examScore float64 // It's also possible to delcare multiple variables of the same type at once
var userName string // This variable can only hold a string value

Variable declaration and assignment

Now that we’ve declared our variables, the next thing is to assign values to them. You can assign any value of that type to a particular variable with = . For instance, let’s assign values to the above variables that we declared.

age = 30
height, examScore = 167.64, 68.6 // Assigning multiple variables at once
userName = "John Doe"

Alternatively, variable declaration and assignment can be done using the shorthand method, in Go if you know what the initial value of a variable is going to be as soon as you declare it, then you can use a short variable declaration. Instead of explicitly declaring the type of the variable and later assigning to it with = , you can do both at once using := . Now let’s redeclare and assign the above variables to values using short variable declaration.

age := 30 // same as var age int = 30
height, examScore := 167.64, 68.6 // same as var height, examScore float64 = 167.64, 68.6
userName := "John Doe" // same as var userName string = "John Doe"

When declaring a variable without specifying an explicit type (either by using the := syntax or var = expression syntax), the variable's type is inferred from the value on the right-hand side and this concept is called Type Inference.

Zero Values

A variable declared without assigning it a value, that variable will contain the zero value. For instance,

var myInt int // The zero value for int type is 0
var myFloat float64 // The zero value for float64 type is 0
var myString string // The zero value for string type is false
var myBool bool // The zero value for bool type is false

Constants

Constants are declared like variables but with the const keyword. It can be character, string, boolean, or numeric values. The caveat about constant variables is that they cannot be declared using the := syntax.

Let’s write a simple program to print the above user information to the terminal.

package main

import (
"fmt"
)
const totalMark = 100 // constant variable
// it is important to note that variables declare here are package scope and can be accessed anywhere in this code
func main() {
// variables declared here are function scope and can only be accessed inside this function
    age := 30
    height, examScore := 167.64, 68.6
    userName := "John Doe"
    fmt.Println(userName)
    fmt.Println("whose height is", height, "at the age of", age)
    fmt.Println("scored",examScore, "/",totalMark)    
}

Output:

John Doe
whose height is 167.64 at the age of 30
scored 68.6 / 100

Identifiers

Go has a simple set of rules that apply to the names of variables, functions, and types:

  • A name must begin with a letter and can have any number of additional letters and numbers

  • If the name of a variable, function, or type begins with a capital letter, it is considered exported and can be accessed from packages outside the current one. This is why P in fmt.Println is capitalized so it can be used from the main package or any other. Similarly, if a variable function or type name begins with a lowercase letter, it is considered unexported and can only be accessed within the current package.

The Go community follows some additional conventions as well:

  • If a name consists of multiple words, each word after the first should be capitalized, and they should be attached without spaces between them, like this: examScore, UserName, and so on. The first letter of the name should only be capitalized if you want to export it from the package. This style is often called a camel case.

  • When the meaning of a name is obvious from the context, the Go community’s convention is to abbreviate it: to use i instead of index, max instead of maximum, and so on.

Data Structure

The basic data structure in Go is as follows:

  1. Array

  2. Slices

  3. Map

  4. Struct

Array

An array is a collection of values that all share the same type (an array of strings, an array of booleans). The values an array holds are called its elements. In Go, once an array is declared, elements can neither be added nor removed from the array. Elements in an array are numbered, starting with 0, an element number is called its index.

var names [lenght of the array] type // General syntax 
var names [7] string // an array to hold 7 values of type string

names[0] = "John" // set the value of first element to John
names[1] = "Doe" // set the value of second element to Doe

fmt.Println(names) // the output will be ["John","Doe"]
fmt.Println(len(names)) // this is to print the lenght of the array and the output will be 2

Using array literals to do short variable declarations with :=

countries := [7] string {"Nigeria", "Portugal", "Austria", "Switzerland", "Poland", "Norway", "Spain"}
fmt.Println(countries[1], countries[4], countries[2]) // the output will be Portugal Poland Austria

scores := [5] int {55,54,66,70,82}
fmt.Println(scores[0], scores[1], scores[3]) // the output will be 55 54 70

it is important to note that the zero value for an array is 0, that is when an array is declared without values, Go sets its value to 0.

Slices

Like arrays, slices are made up of multiple elements of the same type. Unlike arrays, functions are available that allow us to add an extra element to the end of a slice. To declare the type for a variable that holds a slice, you use an empty pair of square brackets, followed by the type of elements the slice will hold. Also, unlike with array variables, declaring a slice variable doesn’t automatically create a slice. Ipso facto, you can call the built-in make function; you pass make the type of the slice you want to create (which should be the same as the type of the variable you’re going to assign it to), and the length of the slice it should create.

var books []string // declare a slice variable
books = make([]string,4) //create a slice with seven strings

//short variable declaration for slice
books := make([]string,4)
books[0] = "The Outlier" // assign a value to the first element
books[1] = "The Atomic Habits" // assign a value to the second element
books[2] = "Game Theory" // assign a value to the third element
books[3] = "The MBA Guide" // assign a value to the fourth element
fmt.Println(books[2]) // the output will be Game Theory

// getting the length of a slice variable
fmt.Println(len(books)) // the output will be 7

// when you know in advance what values a slice will start with, there's no need to call the make function
subjects := []string {"Games", "Chemistry", "Physics"}

// Add onto a slice with the "append" function
subjects = append(subjects, "Mathematics", "Science")
fmt.Println(subjects) // the output will be [Games Chemistry Physics Mathematics Science]

As with arrays, if you access a slice element that no value has been assigned to, you’ll get the zero value for that type back. Unlike arrays, the slice. the variable itself also has a zero value which is nil. That is, the slice variable that no slice has been assigned to will have a value of nil .

floatSlice := make([]float64, 5)
boolSlice := make([]bool, 5)
fmt.Println(floatSlice) // the output will be 0
fmt.Println(boolSlice) // the output will be false

var intSlice []int
fmt.Println(intSlice) // the output will be []int(nil)
var stringSlice []string // the output will be []string(nil)

Map

In Go, a map is a collection where each value is accessed via a key. To declare a variable that holds a map , you type the map keyword, followed by square brackets ([]) containing the key type; just as with slices, declaring a map variable doesn’t automatically create a map ; you need to call make the function (the same function you can use to create slices). Instead of a slice type, you can pass make the type of the map you want to create (which should be the same as the type of the variable you’re going to assign to it).

var contacts map[key type] value type // general syntax

var contacts map[string]int //Declare a map variable
contacts = make(map[string]int) // actually create the map

//using the short variable declaratioin
contacts := make(map[string]int) // create a map and declare a variable to hold it

//Multiple map literal
contacts := make(map[string]int) {
"John": 823,
"Jane": 553,
"Joe": 678,
"Kenny": 990
// it is important to add a comma to the last key, else Go will throw error
}

fmt.Println(ranks["John"]) // the output will be 823
fmt.Println(ranks["Jane"]) // the output will be 553
fmt.Println(contacts["Joe"] // the output will be 678
fmt.Printlin(contacts["Kenny"]) // the output will be 990

Unlike Array and Slices which only let you use integer indexes, You can choose almost any type to use for a map’s keys. Also, just like slices, the zero value for the map variable is nil . If you declare a map variable but don’t assign it a value, its value will be nil . This means no map exists to add new keys and values to. If you try, you’ll get a panic:

// Wrong approach
var nilMap map[int]string
fmt.Printf("%#v\n",nilMap)
nilMap[2] = "two" 
/* The output will be:
map[int]string(nil)
panic: assignment to entry in nil map
*/

// Correct approach
var myMap map[int]string = make(map[int]string) //create a map first
myMap[2] = "two" // and then add values to it
fmt.Printf("%#v\n",myMap) // the output will be map[int]string{2: "two"}

Struct

A struct (short for “structure“) is a value that is constructed out of other values of many different types. Whereas a slice might only be able to hold string values or a map might only be able to hold int values, you can create a struct that holds string values, int values, float64 values, bool values, and more all in one convenient group. A struct is declared using the struct keyword, followed by curly braces. Within the braces, you can define one or more fields which are values that the struct groups together. Each field definition appears on a separate line and consists of a field name, followed by the type of value that field will hold.

// general syntax
struct {
    field1 string
    field2 int
    field3 float64
    field4 bool
}

// a struct with name, age, gender, and isMarried
var userInfo struct {
name string
age int
gender string
isMarried bool
}

// Access struct fields using the dot operator
userInfo.name = "John Doe"
userInfo.age = 18
gender = "male"
isMarried = true
fmt.Println(userInfo.name) // the output will be John Doe
fmt.Println(userInfo.age) // the output will be 18
fmt.Println(userInfo.gender) // the output will be male
fmt.Println(userInfo.isMarried) // the output will be true

Defined types and structs

Type definitions let you create your types. They allow you to make a new defined type based on an existing type.

package main

import (
    "fmt"
)
 type item struct {
    task string 
    isDone bool
}

type user struct {
    name string
    networth float64
}

    func main() {
    var todo item
    todo.task = "Go Shopping"
    todo.isDone = true
    fmt.Println("Todo:", todo.task) // the output will be Todo: Go Shopping
    fmt.Println("Status:", todo.status) // the output will be Status: tru

    var userInfo user
    userInfo.name = "John Doe"
    userInfo.networth = 100.64
    fmt.Println("User Name:", userInfo.name) // the output will be User Name: John Doe
    fmt.Println("User Networth:", userInfo.networth) // the output will be User Networth: 100.64
}

Conclusion

This article introduces Go (Golang) and focuses on its basic concepts like data types, variables, constants, identifiers, and simple data structures. We cover the essential prerequisites, a basic program template, and detailed explanations of Go's data types, including strings, runes, booleans, and numbers. The article also explains variable declaration and assignment, highlighting type inference and zero values in Go. Additionally, it explores Go's main data structures such as arrays, slices, maps, and structs, with code examples to show how they work. In the upcoming article, we'll explore Functions, Pointers, Loops, Methods, and Interfaces in greater depth.

1
Subscribe to my newsletter

Read articles from John O. Emmanuel directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

John O. Emmanuel
John O. Emmanuel

A Versatile Senior Software Engineer with over 5 years of expertise in full-stack development, focusing on React, React-Native, Node.js, Golang, and TypeScript ecosystems. Demonstrated success in spearheading remote teams and architecting high-performance, scalable web and mobile applications. Exceptional communicator skilled in fostering collaboration across diverse time zones and cultural backgrounds. Committed to driving innovation and operational excellence in distributed work environments.