Blocks, Shadows and Control Structureπ
Blocksπ
- Go variables can be declared outside of functions, as parameters to functions, and as local variables within functions
- Each place where declaration happens is called as a block. Variables, constants, types and functions declared outside of any function are placed in package block.
Shadowing Variablesπ
func main() {
x := 10
if x > 5 {
fmt.Println(x)
x := 5
fmt.Println(x)
}
fmt.Println(x)
}
// guess the output ?
// A) Nothing
// B) 10 line one, 5 line two, 5 line three
// C) 10 line one, 5 line two, 10 line three
- Option C is correct
- A shadowing variable is a variable that has same name as a variable in a containing blocks, it blocks to the shadowed variable.
- Any modern IDE will be able to detect this and alert you with warning
- Be careful to use
:=
because it creates and assigns value, so you can mistakenly shadow variables. - Never shadow a package import!
NOTE: There is a universe block in Golang, which actually have definition of 25 keywords used in go like true
and false
or int
and string
etc.
ifπ
Example :
n := rand.Intn(10)
if n == 0 {
fmt.Println("That's too low")
} else if n > 5 {
fmt.Println("That's too big:", n)
} else {
fmt.Println("That's a good number:", n)
}
- Notice how brackets are not used for enclosing conditional
- variables declared inside
if
orelse
statement exists only within that block. What Go adds is declaring variables which is scoped to bothif
andelse
if n := rand.Intn(10); n == 0 {
fmt.Println("That's too low")
} else if n > 5 {
fmt.Println("That's too big:", n)
} else {
fmt.Println("That's a good number:", n)
}
for (4 Ways)π
Complete for Statementπ
- you can leave any part of the for declaration namely : initialisation, comparison and update
Condition-only for statementπ
- equivalent to
while
loop in other languages
Infinite Loopπ
break and continueπ
NOTE: go doesnβt have do
while loop. Best you could do to run your loop at once is use infinite loop with a break condition.
- Go practices recommend not nesting too many level in if condition, it much better have validation type of code that uses continue to early terminate the loop
// Bad Version
for i := 1; i <= 100; i++ {
if i%3 == 0 {
if i%5 == 0 {
fmt.Println("FizzBuzz")
} else {
fmt.Println("Fizz")
}
} else if i%5 == 0 {
fmt.Println("Buzz")
} else {
fmt.Println(i)
}
}
// Improved Version
for i := 1; i <= 100; i++ {
if i%3 == 0 && i%5 == 0 {
fmt.Println("FizzBuzz")
continue
}
if i%3 == 0 {
fmt.Println("Fizz")
continue
}
if i%5 == 0 {
fmt.Println("Buzz")
continue
}
fmt.Println(i)
}
for-range statementπ
evenVals := []int{2, 4, 6, 8, 10, 12}
for i, v := range evenVals {
fmt.Println(i, v)
}
// or equivalent, index is optional
for v := range evenVals {
fmt.Println(v)
}
- note while unpacking range,
i
var is optional
// map iteration
m := map[string]int{
"a": 1,
"c": 3,
"b": 2,
}
for i := 0; i < 3; i++ {
fmt.Println("Loop", i)
for k, v := range m {
fmt.Println(k, v)
}
}
// string iteration
samples := []string{"hello", "apple_Ο!"}
for _, sample := range samples {
for i, r := range sample {
fmt.Println(i, r, string(r))
}
fmt.Println()
}
- Notice something unusuall in output : index - 7 is left out because default iteration is on runes not the bytes
- NOTE: for-range (i, v) is a copy of original array so any update to them doesnβt affect array unless assigned to array back
Labeling for statementsπ
- by default
break
andcontinue
keywords apply tofor
loop that directly contains them. You can label loops to provide context to break and continue
func main() {
samples := []string{"hello", "apple_Ο!"}
outer:
for _, sample := range samples {
for i, r := range sample {
fmt.Println(i, r, string(r))
if r == 'l' {
continue outer // notice how this resolve and continues the outer loop
}
}
fmt.Println()
}
}
switchπ
words := []string{"a", "cow", "smile", "gopher",
"octopus", "anthropologist"}
for _, word := range words {
switch size := len(word); size {
case 1, 2, 3, 4:
fmt.Println(word, "is a short word!")
case 5:
wordLen := len(word)
fmt.Println(word, "is exactly the right length:", wordLen)
case 6, 7, 8, 9:
default:
fmt.Println(word, "is a long word!")
}
}
// a is a short word!
// cow is a short word!
// smile is exactly the right length: 5
// anthropologist is a long word!
- switch in Go are special, Similar to
if
if a variable is declared in switch declaration then it is accessible to all branches fromcase
- By default case doesnβt fall through in Go as compared to other languages, so there is no use of
break
after every case. - Since fall-through is not supported we should pass comma separated values for multiple matchings
Blank Switchesπ
words := []string{"hi", "salutations", "hello"}
for _, word := range words { // we can leave parts from a for statement
switch wordLen := len(word); { // this is blank switch because we didn't specify what value to compare, instead each case defines this comparison
case wordLen < 5:
fmt.Println(word, "is a short word!")
case wordLen > 10:
fmt.Println(word, "is a long word!")
default:
fmt.Println(word, "is exactly the right length.")
}
}
goto (yes :)π
Ever since Edsger Dijkstra wrote βGo To Statement Considered Harmfulβ in 1968, the goto
statement has been the black sheep of the coding family.
- Most programming languages donβt support
goto
- in Go,
goto
statemnt specifies a labeled line of code, and execution jumps to it. However Go stricts jumps that skip over variable declaration and jumps that go into inner or parallel block
func main() {
a := rand.Intn(10)
for a < 100 {
if a%5 == 0 {
goto done
}
a = a*2 + 1
}
fmt.Println("do something when the loop completes normally")
done:
fmt.Println("do complicated stuff no matter why we left the loop")
fmt.Println(a)
}
NOTE: You should try very hard to avoid using goto
. But in the rare situations where it makes your code more readable, it is an option.