Go (also referred to as Golang), is a statically typed language which requires you to define the types of variables when they are initialised. Once assigned to a datatype, they cannot hold data from another type.
Variables will be a familiar topic if you're coming from another programming language. They allow you to store values, pass things around, and conditionally control logic based on inputs.
The other side of the coin are dynamically typed languages which allow you to change the type freely.
Understanding the Syntax
Let's look at a very basic example of how we can create a variable in Go to get familiar with the syntax.
package main
func main() {
var name = "Bob"
}
Reviewing the code above, we have a very minimal Go program. And all we're doing is defining a variable name
and assigning a string "Bob" to it.
We can also use the shorthand syntax :=
to eliminate the need for using var
. The code below is exactly the same as the code above.
package main
func main() {
name := "Harry"
}
You may notice that I haven't assigned a type to the variable. Didn't I say Go was a statically typed language? Go can infer the type of the variable by the data you assign it, so you have no need to assign the type here.
However, should you want to be explicit about it. You can pass in the data type after the name of the variable. Like so:
var name string = "Harry"
You would use this approach when you do not have an initial value and want to assign a value to the variable later on:
var name string
name = "Harry"
In a dynamically typed language, you would be allowed to reassign the value of a variable to another type. For example, if I try to put in a number (integer) after declaring a variable as a string. Go will complain and you will not be able to compile or run the program.
name := "Harry"
name = 500 // Not allowed
Another thing to note is Go expects the data type to follow the name of the variable. This can take a little getting used to if you come from another language that expects the type to precede the variable name.
Handling Numbers
Numbers can be broken up into two categories. Integers, and floating-point numbers (sometimes referred to as floats). If you are unfamiliar with the concept, integers are whole numbers with no fractional part, i.e.: 4, 6, -2, 230, 0. Whereas floating-point numbers hold a fractional value, ie.: 3.141, -582.2, 10.01.
Integers
To create a variable holding an integer, it's as simple as passing in the value at the point of variable declaration.
age := 30 // inferring type automatically
var age int = 30 // declaring variable as an integer
The above example will initialise a variable of type int
. Depending on the CPU architecture, int
will refer to one of the below types and will hold a number from/to the provided ranges.
- 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)
- 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)
If you're on a 64-bit operating system, int
will map to int64
. A 32-bit will be int32
and so forth. In most cases, leaving it as int
will be okay. But if you're keen on optimisation then you have the option to manually set the size of the variable by passing in one of the above types:
var age int8 = 4
Floating-Point Numbers
Much in the same way as integers, floating-point numbers store a fractional value. In Go we can automatically createa floating-point type by initialising the variable with a fractional number.
pi := 3.141 // inferring type automatically
var pi float32 // declaring variable as float
Note that you cannot perform math between integer types and floating-point types.
You would have to define the variable with a fractional part:
a := 12.5
b := 3.0 // b := 3 would error when multiplying a by b
result := a * b
Or you could wrap the integer as another data type by casting to a specific type.
a := 12.5
b := 3
result := a * float64(b)
Handling Booleans
Boolean values can have one of two possible states: true
or false
. Booleans are often used for making decisions in conditional statements, controlling the flow of a program, and expressing logical concepts.
Here's a basic overview of how bool
works in Go:
var isTrue bool = true
isFalse := false
Booleans can be combined and manipulated using various logical operators in Go. Common Boolean operators include:
&&
(logical AND): Returnstrue
if both operands aretrue
.||
(logical OR): Returnstrue
if at least one operand istrue
.!
(logical NOT): Negates a Boolean value, turningtrue
intofalse
and vice versa.
You will also see booleans are commonly used in conditional statements like if
and switch
to determine the flow of a program. For example:
if isTrue {
// This block of code will execute because isTrue is true.
} else {
// This block will not execute.
}
Handling Strings
A string is a data type used in computer programming to represent text or a sequence of characters.
In essence, a string is a collection of characters, which can include letters, numbers, symbols, spaces, and even special characters like newline and tabs.
To define a string, we can wrap text within quotation marks (") to store that value in the variable. This is how you would create a variable containing the phrase, "Hello, World!".
hello := "Hello, World!"
There is another another way, and that's by using backticks (`). We'll get into the differences between the two in a moment.
hello := `Hello, World!`
Based on which quotes we use, determines how the strings are interpreted. By using regular quotation marks ""
, special characters like newline characters are interpreted.
For example, the below will print out "Hello, World" with a newline between both words.
hello := "Hello,\nWorld"
fmt.Println(hello)
Whereas the non-literal verison using backticks (``) will not handle special characters are simply reprint them.
hello := `Hello,\nWorld`
fmt.Println(hello)
Handling Arrays
Arrays allow you to group together multiple values of the same type within a single variable. You must define the size alongside the type definition at initilisation as arrays in Go are fixed size.
To declare an array with 5 integers, first pass in the size followed by the type, then the values to assign into the array within curly braces.
var numbers := [5]int{1, 2, 3, 4, 5}
You can also omit the size and let Go determine the size based on the number of values passed in at initialisation.
numbers := [...]int{1,2,3,4,5}
Accessing Arrays
Elements in an array can be accessed using their zero-based index. For example, to access the first element of the numbers
array:
firstNumber := numbers[0]
This behaviour changes between programming languages but it's more common to see arrays begin at 0 and Go is one example of this.
Now what if you try to access an element that is outside of the array? Or to put this another way, if our array is holding 5 elements. What happens if I try accessing the 5th element (the last element in the array)?
numbers := [...]int{1, 2, 3, 4, 5}
fmt.Println(numbers[5])
Go will refuse to run the program and we'll be unable to build a binary. Since Go knows the size of our array, it also knows when we're accessing an element that is out of bounds.
The above error can be a common error programmers make. Since arrays begin at 0 and the array has elements within it, this would mean the last element in the array is the length of the array-1.
The correct way to do this would be:
numbers := [...]int{1, 2, 3, 4, 5}
fmt.Println(numbers[4])
// Or
fmt.Println(numbers[len(numbers)-1])
Handling Slices
Slices are like arrays, but dynamically sized. This means their size can change after initialisation and are far more flexible than their array counterparts.
To see how this works in actions:
numbers := []int{1, 2, 3, 4, 5}
What sets this apart from the same example from the arrays section above, is we are omitting the size of the array within the square brackets ([]
).
numbers:= []int{1, 2, 3, 4, 5} // Initialise the slice
numbers = append(numbers, 6) // Append 6 to the end of the slice ([1, 2, 3, 4, 5, 6])
fmt.Println(numbers[len(numbers)-1]) // Prints out 6
This example works on a similar example above whilst showing its compatability with arrays. We're also appending a value to the slice to we get an output of 6 instead of 5.
Handling Maps
If you're coming from another language, you may have used maps albeit under another name. Sometimes called dictionaries or hashmaps, store key => value pairs.
Jumping in with an example, here is a map containing two key value pairs.
var ages = map[string]int{"Bob": 22, "Bill": 33}
fmt.Println(ages["Bob"]) // Prints out 22
Let's break it down to the minimum:
var ages = map[key]value{}
We begin by using the keyword map
to say we want to define a map. Next is the [key]
which we can assign a type for the index. In most cases this tends to be a string, but doens't have to be. Then we have the value
which again we pass in a type for. Finally the {}
where we enter any initial data based on the structure oc the map.
Then say we want to define a map that stores people's ages against their name. We want to use the name as the key, so the key will be a string
. The age will be a whole number and so we will use an int
type.
var ages = map[string]int{}
And based on our map, initilise it with the values we want to store in the {string: int}
format.
var ages = map[string]int{"Bob": 22, "Bill": 33}