Now that we have variables, we can begin adding control logic.
Control logic must be contained in a function:
func main() {
x := rand.Int()
if x < 20 {
fmt.Println(x)
}
}
x < 20
is a boolean expression.
You can put any boolean expression in an if statement:
if ((x < 20) && (x != 15)) || (x > 100) {
...
}
You can also include a short statement before the boolean expression:
if x := 12 * 17; x < 20 {
...
}
You can also use else
and else if
:
if x := 12 * 17; x < 20 {
...
} else if x < 30 {
...
} else {
...
}
Switch statements help reduce complexity of many if branches.
i := "Monday"
switch i {
case "Monday":
fmt.Println(":(")
case "Tuesday", "Wednesday":
fmt.Println(":|")
default:
fmt.Println(":)")
}
Let's dissect this:
switch i {
i
is a primary expression that will be compared with eachcase
.
case "Monday":
- If
i
matchesMonday
, execute the block and exit the switch statement.
case "Tuesday", "Wednesday":
- If
i
matches either of these days, execute and exit the switch statement.
default:
- Similar to the
else
block.
Switch statements can be written without the initial expression also, if the case expressions are boolean:
i := 10
switch {
case i < 5:
...
case i > 100 && i < 200:
...
default:
...
}
The last thing switches can be used for is branching on type:
...
x := someFunc()
switch x.(type) {
case nil:
...
case *int, *int32:
...
case string:
...
default:
...
}
This may seem useless now because the type of the variable is obvious, but it will become more clear why this is important when interfaces are introduced.
for
can operate as a 3 statement for loop (similar to c), a while loop, and an infinite loop
for i := 0; i < 5; i++ {
fmt.Println(i)
}
i := 0
for i < 5 {
fmt.Println(i)
i = i + 2
}
for {
...
}
break
allows you to exit a loop before the for condition is met:
for i := 5; i < 200; i++ {
if i > 12 && i % 2 == 0 {
break
}
}
continue
allows you skip the iteration of code and continue at the top of the for loop.
for i := 0; i < 10; i++ {
if i % 2 == 0 {
continue
}
fmt.Println(i) // Only prints odds
}
defer
tells the compiler to run a function before exiting the current function. This is often used to keep cleanup code near instantiating code.
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Create("test.json")
if err != nil {
panic(err)
}
defer f.Close()
fmt.Fprintln(f, "test!") // f.Close has not yet been called
} // f.Close has been called
panic
is used when something has gone wrong, and you don't want to handle the error and continue. If you would like to handle errors, you can use the error
type.
...
func main() {
x := rand.Int()
if x > 100 {
panic("Number too large for function") // A contrived example.
}
someFunction(x)
}
Although panic
is used when you do not want to continue running the program, you can use recover
to do any cleanup tasks necessary before exiting:
...
func main() {
defer func() {
if a := recover(); a != nil {
fmt.Println("RECOVER", a)
}
}()
x := rand.Int()
if x > 100 {
panic("hello")
}
}
recover
needs to be in a deferred function, so it is impossible to return control to the calling function.
- What happens when you have more than one defer in a function?
- Anonymous functions and defer work great together.