Variables in Go
Variables are named values stored in specific areas of memory and used during program execution.
Go (also known as Golang) is a statically typed programming language. This means that once a variable is declared, its type is fixed and cannot be changed.
Variables can have various types, each with its own purpose and characteristics.Go provides several basic data types, which form the foundation of the language's logic:
Integer
Floating-point
String
Boolean
Additionally, Go supports composite data types:
Arrays
Slices
Structures
Maps
There are also several auxiliary types:
Pointers
Interfaces
Besides these, Go (similar to C++) includes a Standard Library (std) containing many predefined types.
You can find more detailed information about variable types in Go in a separate article. For instructions on installing Go on Linux, Windows, or macOS, refer to the Hostman guides.
All the code examples in this tutorial were tested using Go version 1.21.3.
Compiling and Running Code
All the code examples in this guide are run in separate files with the .go extension.
First, create a new file:
sudo nano example.go
Next, fill it with code inside the main() function, including any necessary modules:
package main
import "fmt"
func main() {
// start of example
var number int = 10
fmt.Println(number)
// end of example
}
Then run the file:
go run example.go
Declaring a Variable
There are different ways to declare a variable in Go before using it—ranging from a full form, explicitly specifying the parameters (or multiple parameters) of the variable, to a shorthand form that uses automatic type inference and initialization.
The choice of declaration method depends on the context. However, it’s generally recommended to use the most concise and automatic form whenever possible, as this reduces the likelihood of programmer errors by shifting some of the responsibility to the language's interpreter.
Using the var Keyword
The most explicit way to declare a variable in Golang is by using the var keyword, followed by the variable name, type, and value:
var some_variable int = 5
However, if the variable is initialized with a value, you can omit the explicit type:
var some_variable = 5
You can also declare a variable without assigning a value, but in this case, you must specify the type:
var some_variable intsome_variable = 5
In all of these examples:
var — the keyword for declaring a variable
some_variable — the variable's name
int — the variable's type
5 — the variable's value
For example, this is how you can declare string variables:
var some_name string = "John"
The following declaration will result in an error:
// ERROR: no value or type specified during declarationvar some_namesome_name = "John"
It’s important to note that type inference is only possible during the initial declaration of the variable when the interpreter allocates the appropriate amount of memory for its value.
Short Form :=
Despite Go's strict static typing, it allows variables to be declared in a more concise form without explicitly specifying their parameters:
some_variable := 5
In this case, the interpreter understands that it needs to automatically infer the variable type based on the assigned value.
However, this shorthand declaration is only allowed inside a function (including main()); it cannot be used outside a function:
package main
// ERROR: short form declaration outside of a function
some_variable := 5
func main() {
// OK: short form declaration inside a function
other_variable := 10
}
It’s important to understand the distinction between declaring a variable (with initialization) and assigning a value to it:
package main
func main() {
some_variable := 5 // this is declaration and initialization (colon is present)
some_variable = 50 // this is assignment (no colon)
other_variable = 7 // ERROR: this is assignment (no colon) to an undeclared variable
}
For example, you can declare (and initialize) several variables sequentially:
age := 50 // variable of type int
name := "John" // variable of type string
occupation := "Just a guy" // variable of type string
height := 190.5 // variable of type float32
You cannot use the := operator together with the var keyword. Doing so will result in an error:
var someVariable int := 5 // ERRORvar someVariable := 5 // ERROR
Excluding the var keyword but still explicitly specifying the type will still result in an error:
someVariable int := 5 // ERROR
Multiple Variables
In Go, you can declare multiple variables in one line or block.
For example, you can use the var keyword with a single type for all declared variables:
var width, height, depth int = 100, 200, 300
You can also separate the declaration of variables and their assignment:
var width, height, depth intwidth, height, depth = 100, 200, 300
If the variable types differ, the interpreter can automatically infer their types:
var name, age, fired = "John", 50, false
Similarly, you can use the short form for multiple variables:
name, age, fired := "John", 50, false
In this case, there is no var keyword, nor are the types of the variables specified.
Another way to declare multiple variables is by using a block:
var (
name string = "John"
age int = 50
height float64 = 190
fired bool = false
)
By the way, you can format block declarations using spaces in such a way that names, types, and values align in columns, improving code readability:
var (
name string = "John"
age int = 50
height float64 = 190.5
fired bool = false
)
The block declaration has no particular utility significance. It’s just syntactic sugar that:
Improves code readability by grouping important variables in one place.
Improves code cleanliness by avoiding repeated use of the var keyword for each variable.
Improves code maintainability by simplifying the search and modification of variable parameters.
Thus, block declaration is justified only when you need to group several key variables, simplifying their visual perception in a code editor.
No Initialization
In Go, it is possible to create a variable without initializing it. In this case, the variable is assigned a zero value corresponding to the specified type:
For int, float32, float64: 0, 0.0, 0.0
For bool: false
For string: ""
For pointers: nil
We can demonstrate this behavior of Go regarding variable declaration and initialization in the following script:
package main
import "fmt"
func main() {
// Integer
var numberInt int
fmt.Println("Integer:", numberInt)
// Floating-point number
var numberFloat float32
fmt.Println("Floating-point number:", numberFloat)
// String
var text string
fmt.Println("String:", text)
// Boolean
var condition bool
fmt.Println("Boolean:", condition)
// Array
var array [5]int
fmt.Println("Array:", array)
// Slice
var cut []int
fmt.Println("Slice:", cut)
// Struct
type S struct {
name string
size int
address string
}
var structure S
fmt.Println("Struct:", structure)
// Map
var dictionary map[int]int
fmt.Println("Map:", dictionary)
// Pointer
var pointer *int
fmt.Println("Pointer:", pointer)
}
The console output will be as follows:
Integer: 0
Floating-point number: 0
String:
Boolean: false
Array: [0 0 0 0 0]
Slice: []
Struct: { 0}
Map: map[]
Pointer: <nil>
As you can see, variables of different types are automatically initialized with zero (or empty) values wherever possible.
Naming Conventions
In Golang, variable names can either start with a Latin letter or an underscore (_):
onething := 123 // OK
Onething := 123 // OK
_onething := 123 // OK
__onething := 123 // OK
1thing := 123 // ERROR
Additionally, variable names have a functional feature: names starting with an uppercase letter are visible in other packages, while names starting with a lowercase letter are not.
There are also several universal naming conventions across programming languages, including Go:
Snake Case
Camel Case
Pascal Case
Kebab Case (not supported in Go)
Snake Case
In Snake Case, the variable name looks like this:
some_random_variable := 123 // lowercaseSOME_RANDOM_VARIABLE := 123 // uppercase
Camel Case
In Camel Case, the variable name looks like this:
someRandomVariable := 12
Pascal Case
In Pascal Case, the variable name looks like this:
SomeRandomVariable := 123
Kebab Case
In Kebab Case, the variable name looks like this:
// ERRORsome-random-variable := 123 // lowercaseSOME-RANDOM-VARIABLE := 123 // uppercase
However, Go doesn't support the Kebab Case style due to the hyphen character, which is reserved for the subtraction operation.
Example: Declaring Multiple Variables
Let’s further explore all the aforementioned ways of declaring variables in Golang in this script example:
package main
import "fmt"
func main() {
// Explicit declaration with type specification
var age int = 50
fmt.Println("Age:", age)
// Explicit declaration with type inference
var height = 190.5
fmt.Println("Height:", height)
// Short declaration
name := "John"
fmt.Println("Name:", name)
// Explicit declaration of multiple variables
var width, depth int = 100, 200
fmt.Println("Width:", width, "Depth:", depth)
// Explicit declaration without initialization
var distance int
fmt.Println("Distance:", distance)
// Block declaration of multiple variables
var (
occupation string = "Welder"
category float32 = 3.4
license bool
)
fmt.Println("Occupation:", occupation, "Category:", category, "License:", license)
}
The result of running this code will be the following output in the console:
Age: 50
Height: 190.5
Name: John
Width: 100 Depth: 200
Distance: 0
Occupation: Welder Category: 3.4 License: false
The var keyword is required for explicit variable declaration, especially in the global scope.
The := operator is used for short variable declarations, particularly within functions.
The block () syntax is used for readable declaration of multiple variables.
It's important to remember that Go emphasizes minimalism and concise syntax. Therefore, the most compact form of notation should be used wherever possible.
This reduces errors and issues while maintaining the cleanliness and readability of the code.
Variable Initialization
Typically, when a variable is declared, it is manually initialized with a specific value. The initialization of different types has syntactic differences.
Number
Numerical variables are initialized by assigning a numerical value, which is syntactically simple:
// int
var someNumber int = 5
// float32
otherNumber := 10.0
A number can be initialized with another number:
// int
var someNumber int = 5
var otherNumber int = someNumber
// int
oneMoreNumber := someNumber
String
String variables are initialized by assigning a sequence of characters enclosed in double quotes:
// stringvar someString string = "Some programmer was here"
A string can also be initialized with another string:
// string
var someString string = "Some programmer was here"
var otherString string = someString
// string
oneMoreString := someString
Boolean
Initializing boolean variables is similar to initializing numeric and string variables, except that the value used is the keyword true or false:
// boolvar someBool bool = true
Similarly, boolean variables can be initialized with other boolean variables:
// bool
var someBool bool = true
var otherBool bool = someBool
// bool
oneMoreBool := someBool
Array
There are several ways to initialize an array. The simplest one is through sequential access to the elements:
// array
var languages [3]string
languages[0] = "Golang"
languages[1] = "Python"
languages[2] = "Rust"
A more complex method is using a composite literal. A composite literal is a compact syntax for initializing any composite (struct-like) type, which avoids assigning each element individually.
Thus, the array can be initialized in one step:
var languages = [3]string{"Golang", "Python", "Rust"}
Or using the shorthand form:
languages := [3]string{"Golang", "Python", "Rust"}
You can also partially initialize array elements:
// array size 5, but only 3 elements initialized
languages := [5]string{"Golang", "Python", "Rust"}
languages[3] = "Java"
languages[4] = "C++"
To make the initialization of a large array more readable, you can format it like this:
languages := [5]string{
"Golang",
"Python",
"Rust",
"Java",
"C++", // the comma at the end is REQUIRED
}
By the way, an array can be initialized with another array, copying all of its elements:
languages := [3]string{"Golang", "Python", "Rust"}otherLanguages := languages
It’s important to understand that copying an array also occurs when it is passed to a function:
package main
import "fmt"
func change(languages [5]string) {
for i := range languages {
languages[i] = "[" + languages[i] + "]"
}
}
func main() {
languages := [5]string{
"Golang",
"Python",
"Rust",
"Java",
"C++",
}
change(languages)
fmt.Println(languages)
}
The output in the console will be:
[Golang Python Rust Java C++]
Thus, only the copy of the array inside the change() function was modified, not the original array from the main() function.
However, explicit initialization of an array with another array is possible only if both arrays have the same length and type:
languages := [3]string{"Golang", "Python", "Rust"}
var otherLanguages [3]string = languages // OK
var oneMoreLanguages [4]string = languages // ERROR
Additionally, in Go, you can create arrays from an arbitrary number of other arrays. You can initialize elements of such arrays both sequentially:
var matrix [2][2]string
matrix[0][0] = "a"
matrix[0][1] = "b"
matrix[1][0] = "c"
matrix[1][1] = "d"
Or using a composite literal:
var matrix = [2][2][2]string{{{"a", "b"}, {"c", "d"}}, {{"e", "f"}, {"g", "h"}}}
As shown, the second option takes up less space, but the syntax is more complex.
Slice
A slice is initialized the same way as an array:
var languages = []string{"Golang", "Python", "Rust"}
However, unlike an array, a slice can be initialized with another slice of arbitrary length:
var languages = []string{"Golang", "Python", "Rust"}var otherLanguages []string = languages
Map
Maps are initialized using a composite literal with the type of the key and value specified. The content is listed using commas and separated by a colon:
var languages = map[string]string{"first": "Golang", "second": "Python", "third": "Rust"}
You can also use the shorthand declaration and a more readable initialization format:
languages := map[string]string{
"first": "Golang",
"second": "Python",
"third": "Rust", // the comma at the end is MANDATORY
}
However, initializing a map with another map does not copy the elements; instead, it makes them shared:
package main
import "fmt"
func main() {
languages := map[string]string{"first": "Golang", "second": "Python", "third": "Rust"}
otherLanguages := languages
fmt.Println(languages)
fmt.Println(otherLanguages)
otherLanguages["first"] = "C++"
fmt.Println(languages)
fmt.Println(otherLanguages)
delete(otherLanguages, "second")
fmt.Println(languages)
fmt.Println(otherLanguages)
}
The console output of this example will be:
map[first:Golang second:Python third:Rust]
map[first:Golang second:Python third:Rust]
map[first:C++ second:Python third:Rust]
map[first:C++ second:Python third:Rust]
map[first:C++ third:Rust]
map[first:C++ third:Rust]
Pointer
Pointers can only be initialized with the address of a variable of the same type:
var variable int = 15var pointer *int = &variable
The ampersand (&) symbol is used to get the address of any variable:
package main
import "fmt"
func main() {
var variable int = 15
var pointer *int = &variable
fmt.Println(pointer)
}
The console output of this example will look something like:
0xc000104040
You can also use shorthand notation to initialize pointers:
variable := 15pointer := &variable
To access the value stored at the address of a pointer, you need to dereference it using the asterisk (*):
package main
import "fmt"
func main() {
var variable int = 15
var pointer *int = &variable
fmt.Println(*pointer)
}
In this case, the console output will show:
15
Thus, you can assign new values to a variable located at the address of the pointer:
package main
import "fmt"
func main() {
var variable int = 15
var pointer *int = &variable
*pointer = 5
fmt.Println(*pointer)
}
The console will display:
5
Finally, a pointer can be initialized with an anonymous object in memory. This is done using the new() function, which returns the address of the allocated memory:
variable := new(int)*variable = 15
You don't need to manually delete the allocated memory — the garbage collector automatically handles this.
Structure
A structure can be initialized either with explicitly specified values in order:
type something struct {
first string
second int
}
var structure something = something{"John", 15}
Or with explicitly specified values by key names:
type something struct {
first string
second int
}
var structure something = something{second: 15, first: "John"}
Alternatively, you can choose not to specify any values, which will automatically initialize all fields to their zero values:
package main
import "fmt"
type something struct {
first string
second int
}
func main() {
var structure something = something{}
fmt.Println(structure)
structure.first = "John"
structure.second = 15
fmt.Println(structure)
}
In this case, the console output will be:
{ 0}
{John 15}
Branching Based on Variables
Variables play a central role in branching. Different parts of the program's code are executed based on their values (conditions).
if/else
The most basic conditional construct is created using the if/else statements. Here's the simplest condition:
a := 5
b := 10
if a < b {
fmt.Println("A is less than B")
}
For example, you can use a simple condition to check a pointer:
var pointer *int
if pointer == nil {
fmt.Println("No address")
}
A more complex form would look like this:
a := 10
b := 5
if a < b {
fmt.Println("A is less than B")
} else {
fmt.Println("A is greater than B")
}
You can create even more complex constructs by combining else and if:
a := 10
b := 5
if a < b {
fmt.Println("A is less than B")
} else if a > b {
fmt.Println("A is greater than B")
} else {
fmt.Println("A is equal to B")
}
Multiple if/else expressions can be used:
a := 12
if a < 5 {
fmt.Println("A is less than 5")
} else if a < 10 {
fmt.Println("A is less than 10")
} else if a < 20 {
fmt.Println("A is less than 20")
} else {
fmt.Println("A is in superposition")
}
switch
Another way to branch is using the switch construct, where possible values of a variable are defined, and actions are performed if there's a match:
a := 1
switch a {
case 0:
fmt.Println("A is 0")
case 1:
fmt.Println("A is 1")
case 2:
fmt.Println("A is 2")
}
The default section can be used to define an action that runs if no match occurs:
a := 3
switch a {
case 0:
fmt.Println("A is 0")
case 1:
fmt.Println("A is 1")
case 2:
fmt.Println("A is 2")
default:
fmt.Println("A is in superposition")
}
You can also combine multiple possible matches into one section:
a := 1
switch a {
case 0, 1, 2:
fmt.Println("A is either 0, 1, or 2")
default:
fmt.Println("A is in superposition")
}
Useful Functions
Go has many utility functions for working with variables. In this guide, we'll cover just the basic ones.
Environment Variables
Go provides special system functions that allow you to set and get environment variables:
package main
import (
"fmt"
"os"
)
func main() {
os.Setenv("SOMEVAR", "1") // Set an environment variable
fmt.Println("SOMEVAR:", os.Getenv("SOMEVAR")) // Read an environment variable
}
Time Variables
Often, the program logic requires measuring time. Go has a corresponding tool for this — the time type.
Time is a broad topic by itself. To learn more about the time package, you can check the official documentation. This guide will show how to get the current time in different formats:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Current time:", time.Now())
fmt.Println("Current time (UTC):", time.Now().UTC())
fmt.Println("Current time (Unix):", time.Now().Unix())
}
The console output will look something like this:
Current time: 2009-11-10 23:00:00 +0000 UTC m=+0.000000001
Current time (UTC): 2009-11-10 23:00:00 +0000 UTC
Current time (Unix): 1257894000
You can also specify specific time parameters:
package main
import (
"fmt"
"time"
)
func main() {
timeNow := time.Now()
fmt.Println("Full time:", timeNow)
fmt.Println("Year:", timeNow.Year())
fmt.Println("Month:", timeNow.Month())
fmt.Println("Day:", timeNow.Day())
fmt.Println("Hour:", timeNow.Hour())
fmt.Println("Minutes:", timeNow.Minute())
fmt.Println("Seconds:", timeNow.Second())
}
In this case, the console output will be:
Full time: 2024-11-15 23:46:09.157929822 +0000 UTC m=+0.000031801
Year: 2024
Month: November
Day: 15
Hour: 23
Minutes: 23
Seconds: 9
Adding and Removing Elements from a Slice
You can add elements to slices:
var languages = []string{"Golang", "Python", "Rust"}
languages = append(languages, "Java", "C++")
fmt.Println(languages)
This will append "Java" and "C++" to the languages slice.
You can also remove elements from slices:
var languages = []string{"Golang", "Python", "Rust"}
// Remove the 2nd element (index 1)
n := 1
languages = append(languages[:n], languages[n+1:]...)
fmt.Println(languages)
In this example, the second element is removed from the languages slice using slice operators, which create a new sequence from parts of the original slice.
Here’s an example of slicing a sequence:
package main
import "fmt"
func main() {
var sequence = []string{"One", "Two", "Three", "Four", "Five"}
newSequence := sequence[1:4] // Elements from index 1 to 3 become the new slice
fmt.Println(newSequence)
}
The output in the console will be:
[Two Three Four]
Checking the Type of a Variable
You can check the type of a variable using the TypeOf() function from the reflect package:
package main
import (
"fmt"
"reflect" // Package to determine the type
)
func main() {
variableString := "string"
variableInt := 5
variableFloat64 := 1.5
variableBool := true
fmt.Println(reflect.TypeOf(variableString))
fmt.Println(reflect.TypeOf(variableInt))
fmt.Println(reflect.TypeOf(variableFloat64))
fmt.Println(reflect.TypeOf(variableBool))
}
The console output for this example will be:
string
int
float64
bool
Variables in Strings
Often, you need to insert a variable into a string. There are several ways to do this:
package main
import "fmt"
func main() {
// METHOD 1
stringPre := "human-readable"
stringEnd1 := fmt.Sprintf("This is a %s string", stringPre)
fmt.Println(stringEnd1)
// METHOD 2
stringEnd2 := "This is " + stringPre + " string"
fmt.Println(stringEnd2)
}
The output in the console will be:
This is a human-readable stringThis is a human-readable string
You can also combine numeric variables with strings:
package main
import "fmt"
func main() {
name := "John"
age := 50
fmt.Printf("Hi, my name is %v and I'm %v years old.\n", name, age)
}
The output will be:
Hi, my name is John and I'm 50 years old.
Conclusion
Like in most other programming languages, variables in Go are essential for storing data. Since data types differ from each other, Golang variables have several basic types, each having a specific representation in the computer's memory.
In this guide, we only covered the basic ways to work with variables. You can find more detailed (and comprehensive) information about types and their specifics in the official Golang documentation.
Additionally, the official Go package manager catalog provides information on many useful modules available for import into your project. One such module is the Standard Library.
Check out our app platform to deploy Go applications (such as Beego and Gin).
27 January 2025 · 19 min to read