Deep Dive into Go: Control Statements, Packages, Declarations, and Operators
Table of contents
We’ve touched on these topics before, but let’s circle back and explore them in greater detail. Specifically, we'll look at control statements, packages, declarations, and operators. Buckle up, it’s gonna be fun!
Control Structures in Go
If Statements
The if
statement is a basic control structure in Go. One thing to note is that braces {}
are mandatory, even if you have a single statement inside the if
. Here's a basic example:
if condition {
// your code here
}
This is not a choice; you can't skip the braces. The braces have to be laid out in the Launcher Base Style (1TBS), which is enforced by the compiler.
You can also have a short variable declaration in the if
statement, like this:
if err := someFunction(); err != nil {
// handle error
}
Here, err
is declared and assigned the result of someFunction()
, and then checked for the condition err != nil
.
For Loops
Go only has one looping construct, the for
loop. It can be used in several ways:
- Classic for loop:
for i := 0; i < 10; i++ {
fmt.Println(i)
}
This loop has three parts separated by semicolons:
Initialization (
i := 0
)Condition (
i < 10
)Post iteration increment (
i++
)
- While loop (kind of):
for condition {
// your code here
}
This behaves like a while
loop found in other languages.
- Infinite loop:
for {
// your code here
}
This starts an infinite loop which continues until you explicitly break out of it.
- Range loop:
// Iterating over a slice
slice := []int{1, 2, 3}
for i, v := range slice {
fmt.Println(i, v)
}
// Iterating over a map
myMap := map[string]int{"a": 1, "b": 2}
for k, v := range myMap {
fmt.Println(k, v)
}
The range
operator returns one or two values. For slices, it returns the index and the value at that index. For maps, it returns the key and the value for that key. One important detail is that the value is copied, which is efficient for small types but might not be for larger types.
Switch Statements
Switch statements in Go are a cleaner way to handle multiple conditions. They break automatically after each case, unlike in some other languages where you need a break
statement.
switch day {
case "Monday":
fmt.Println("Start of the week!")
case "Friday":
fmt.Println("Almost weekend!")
default:
fmt.Println("Just another day")
}
Switch cases are evaluated from top to bottom, and the default
case is evaluated last no matter where it is placed in the switch statement.
You can also switch on true for more complex conditions:
switch {
case age < 18:
fmt.Println("Minor")
case age >= 18 && age < 60:
fmt.Println("Adult")
default:
fmt.Println("Senior")
}
This form of the switch statement is like a series of if-else
statements.
Packages
Every Go file belongs to a package. The main package is special because it defines the entry point for the executable:
package main
import "fmt"
func main() {
fmt.Println("Hello, Go!")
}
Packages help in organizing code and managing dependencies. Here’s how you import a package:
import "fmt"
You can have multiple files in a package, and each file must import the necessary dependencies individually.
Exported vs Unexported Identifiers
In Go, if an identifier (variable, type, function, etc.) starts with a capital letter, it is exported and can be accessed from other packages. If it starts with a lowercase letter, it is unexported and private to the package.
// In package mypackage
var ExportedVar = "I can be accessed from other packages"
var unexportedVar = "I am private to mypackage"
Declarations
Go supports different kinds of declarations, including constants, types, and variables.
Variable Declarations
Variables can be declared using the var
keyword or the short declaration :=
:
var a int = 10
b := 20
You can also declare multiple variables in a block:
var (
x int
y = 20
z int = 30
)
Short Declaration Operator
The short declaration :=
can only be used within functions:
func main() {
a := 10
fmt.Println(a)
}
It’s also the only way to declare a variable at the beginning of a control structure like if
:
if a := someFunction(); a > 10 {
fmt.Println("Greater than 10")
}
The short declaration operator requires at least one new variable to be declared. If all variables on the left side of :=
are already declared in the same scope, it will be treated as an assignment.
Here's an example demonstrating a common mistake:
func main() {
var err error
// Incorrect usage, redeclaring err
err := someFunction() // This line will cause a compile error
}
To avoid this, you must ensure at least one new variable is introduced:
func main() {
var err error
if x, err := someFunction(); err != nil {
fmt.Println(x)
}
}
Shadowing
Shadowing occurs when a variable declared in an inner scope has the same name as a variable in an outer scope. Here's an example where shadowing causes a bug:
func main() {
var err error
for {
err := someFunction()
if err != nil {
break
}
}
if err != nil {
fmt.Println("Error occurred")
}
}
In this case, the err
inside the for
loop is a different variable from the err
in the outer scope. The outer err
remains nil
, leading to incorrect behavior.
Types
Go is a statically typed language, meaning variables need to have a type. There are two main typing approaches: structural typing and named typing.
Structural Typing
This is based on the structure or behavior of types:
type Person struct {
Name string
Age int
}
var p Person
Named Typing
This is when you explicitly introduce a new type:
type Age int
var myAge Age = 25
var yourAge int = 30
// This won't work
// myAge = yourAge
// You need to convert the type
myAge = Age(yourAge)
Named types create distinct types even if they share the same underlying type. This helps in creating more readable and maintainable code by providing context-specific types.
Operators
Go has a simple set of operators grouped into arithmetic, comparison, and logical operators.
Arithmetic Operators
a := 10
b := 5
fmt.Println(a + b) // 15
fmt.Println(a - b) // 5
fmt.Println(a * b) // 50
fmt.Println(a / b) // 2
fmt.Println(a % b) // 0
Comparison Operators
fmt.Println(a == b) // false
fmt.Println(a != b) // true
fmt.Println(a > b) // true
fmt.Println(a < b) // false
fmt.Println(a >= b) // true
fmt.Println(a <= b) // false
Logical Operators
trueVal := true
falseVal := false
fmt.Println(trueVal && falseVal) // false
fmt.Println(trueVal || falseVal) // true
fmt.Println(!trueVal) // false
Precedence
Operator precedence determines how an expression is evaluated. Use parentheses to make expressions clear:
result := (3 + 4) * 5 // 35
Conclusion
That wraps up our deep dive into some basic Go concepts. We’ve explored control structures, packages, declarations, and operators. This should give you a solid foundation for writing Go programs. In the next segment, we’ll cover I/O and file operations. Stay tuned for more Go goodness!
Subscribe to my newsletter
Read articles from Arya M. Pathak directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Arya M. Pathak
Arya M. Pathak
Backend Dev | VIT Pune '26 | 🐈Cat Connoisseur /ᐠ。ꞈ。ᐟ\