Hi everyone! this is Jimmy , and this is the third article in my series “Breaking Things with Go.” In this series, I document my journey through Jon Bodner’s Second Edition: Learning Go – An Idiomatic Approach to Real-World Go Programming and explore how to use Go in the most practical way I can

in this series the resources are the book itself, go documentation, and any AI model to clarify some things

lets Jump into it Jump Into A Hole Stickers - Find & Share on GIPHY


enough with the composite types already (I’m done believe me) so lets talk about something else for a change (or maybe cause the chapter is already done) who knows ??

this chapter will be mainly about the Control Structure but we can’t go through if statements and loops and goto statements without explaining the Blocks and Shadowing first so we would pay attention to it later

Blocks and Shadowing

just like most programming languages blocks exist

  • all variables defined at the top level of a function are in block
  • with in a function each braces defines another block
  • control statements are also blocks on their own

you can access identifiers defined in outer block within any inner block but now the other way around

Shadowing

the variable shadowing happens when you declare an identifier in an outer block and re-declare it under the same name in an inner block

for example

go
    name := "Dan"
    if name == "Dan" {
        fmt.Println(name) // Dan
        name := "alex"
        fmt.Println(name) // alex
    }
    fmt.Println(name) // Dan

as long as the shadowing variable exists (meaning we are still in the same block after shadowing) → no way to access the shadowed variable "Dan"

  • and the shadowing ends where the close braces found then you access the original value again

the shadowing happens by re-declaring so you have to use either var or := but using just equal operator in the inner block is just reassigning and it will affect the actual value

go
x := 10 
if x < 15{
    fmt.Println(x) //10
    x = 12
    fmt.Println(x) //12
}
fmt.Println(x) //12

that's why we said using := for declaration isn't safe cause it can make it unclear what variables are being used and we can shadow one of them by accident

you have to make sure you don't shadow package import like this for example

go
package main
import "fmt"

func main(){
    x := 10 
    fmt.Println(x) // print 10 normally 
    fmt := "oops" // this shadows the package named fmt in the file block
    fmt.Println(fmt) // gets error cause the declared variable `fmt` doesn't have field called Println  
}

the problem isn't the definition of fmt as variable but is trying to access non-existing field called Println so how that happens ? isn't fmt some kind of keyword that i can't use ? no it isn't a keyword

The Universe Block

go has 25 keywords only and guess what ? int, string, true, false, make, close, nil aren't included in the list they are pre-declared identifiers and they are defined in what's known as the universe block (the block which contains all other blocks)

because they declared in the universe block (which is outer block from main block perspective) we can shadow them without any issue for example

go
fmt.Println(true) // prints true boolean value 
true := 10 
fmt.Println(true) // 10

so don't use them as identifiers names cause they will cause bugs and strange behavior and what is worse is they will be very hard to track

If Statement

  • in go we don't put the condition in parentheses
  • any variable declared within the brace of an if or else statement exists only within that block
  • we can declare variables that are scoped to the condition and to both if and else blocks
bash
if variables declaration; condition {
    #execute if true
} else if another condition {
    #execute if true
} else{
    #execute if others not true
}
  • as you can see you can declare variables while writing if statement

    • but you can declare them using := only var isn't allowed
    • declaring variables under the same name in that place still considered shadowing cause this is a new block now
  • the else if and else block has to be on the same line as the closing braces of the previous block

go
    if n := rand.Intn(10); n == 0 { // n is accessible only through the if, else if, else blocks
        fmt.Println(n)
    } else if n > 5 {
        fmt.Println("number is higher than 5")
    } else {
        fmt.Println("good number")
    }
    fmt.Println(n) // compiling error undeclared name

Takes

  • Blocks define scope so variables are only accessible within the block they’re declared in but inner blocks can read outer blocks
  • Shadowing is powerful but dangerous re-declaring a variable using := or var in an inner block hides the outer one, which can easily lead to bugs especially when shadowing imports or pre-declared identifiers (from the universe block)
  • if statements create their own scope, variables declared in an if condition exist only within the if / else if / else chain, and using the same name there still counts as shadowing

Coming Next

the next break will be about for loops and maybe switch statements (based on the content length don’t wanna make it too long to read)
so stick around for next break where we will break more stuff

feel free to reach out to me Peace Out GIFs | Tenor