Chapter 4 Blocks, Shadows, and Control Structures
Blocks (From Outer to Inner)
- Universe block: all pre-declared identifiers (
int,true, etc. - they are not keywords) live in the universe block. - Package block: variables, constants, types, and functions declared outside of any functions are placed in the package block.
- File block (one per file within the package):
importstatements define names from other packages for use in the file block. - Function block: all variables defined at the top level of a function (including parameters) are in the function block.
- Within a function, every set of braces
{}defines another block, includingif,for, andswitchstatements.
Shadowing Variables
- A shadowing variable has the same name as a variable in a containing block. For as long as the shadowing variable exists, you cannot access a shadowed variable.
- It is very easy to accidentally shadow a variable with
:=: it only reuses variables declared in the current block.
- It is very easy to accidentally shadow a variable with
- Identifiers from the universe block and package block can be shadowed - be careful not to do so.
go vetdoes not report shadowing as a likely error. Refer to Chapter 11 for third-party tools to detect it.
if
- Go supports declaring variables scoped to the condition and to both the
ifandelseblocks - this is a special block.
if n := rand.Intn(10); n > 5 {
fmt.Println(n - 5)
} else {
fmt.Println(n)
}
for, Four Ways
The Complete for Statement (C-style)
for init; condition; post { ... }- You must use
:=to initialize new variables:varis not legal here. - One or more of the three parts can be left out.
- You must use
The Condition-Only for Statement (while)
for condition { ... }
The Infinite for Statement (while true)
for { ... }
break and continue
breakexits the loop immediately.- An infinite loop and a
if-breakcan be used to simulatedo/while.
- An infinite loop and a
continueproceeds to the next iteration.- Go encourages short
ifstatement bodies withcontinueorbreakinstead of nested code.
- Go encourages short
The for-range Statement (Iterators)
for-rangeloops iterate over built-in data structures (strings, arrays, slices, maps, and channels).for i/k, v := range vals { ... }:i/kis the index/key,vis the value.for _, v := range vals { ... }: ignore the index/key.for i/k := range vals { ... }: only get the index/key.
- Iterating over maps does not guarantee a specific order.
- Fixed map ordering has the following problems:
- Writing code that depends on a specific order of maps.
- Hash DoS attacks: sending keys that all hash to the same value to degrade performance.
- The following modifications were made:
- The hash algorithm for maps includes a random number generated on creation.
- The order of
for-rangeover a map varies a bit on each execution.
- Exception: the formatting functions (
fmt.Print, etc.) always output maps with keys in ascending order.
- Fixed map ordering has the following problems:
for-rangeiterates over runes instead of bytes in strings. The offset is incremented by the number of bytes in the rune, and the offset (the number of bytes from the beginning) and the rune value are returned.- The
for-rangevalue is a copy. Modifying it does not modify the underlying data structure.
for Loop Scoping Changes
- Prior to Go 1.22, the loop variables are per-loop scope: they are created once and reused on each iteration. This may lead to unexpected behavior with goroutines or functions.
- Go 1.22 fixes this behavior by creating new variables on each iteration:
- For C-style
forloops, a new variable is created and initialized with the value in the previous iteration. - For
for-rangeloops, new variables are created and initialized with the next value from the underlying data structure.
- For C-style
- This is a backward-breaking change.
for _, v := range x {
fmt.Printf("%p\n", &v) // different address on each iteration
}
Labeling Your for Statements
breakandcontinuecan exit or skip over an iterator of an outer loop with labels.
outer:
for _, outerVal := range outerVals {
for _, innerVal := range outerVal {
// process innerVal
if invalidSituation(innerVal) {
continue outer
}
}
}
Choosing the Right for Statement
- Use a
for-rangeloop for most cases. - Use a C-style
forloop when not iterating over the entire data structure. - The infinite
forloop may be used to implement the iterator pattern.
Expression switch
- You may declare a variable scoped to all branches of the
switchstatement. - Unlike C, each
caseis a separate block. - Cases in
switchstatements don’t fall through. For multiple values triggering the same logic:- Separate multiple matches with commas:
case 1, 2, 3:. - Use
fallthroughto explicitly continue to the next case. Shouldn’t be commonly used.
- Separate multiple matches with commas:
breakcan be explicitly used to exit early from a case. Shouldn’t be commonly used.switchcan be used on any type comparable with==.
Blank Switches
- A blank
switchallows using any boolean comparison for eachcase.- Specially scoped variables can be declared as well:
switch x := v; { ... }.
- Specially scoped variables can be declared as well:
switch {
case x < 0:
fmt.Println("x is negative")
case x == 0:
fmt.Println("x is zero")
default:
fmt.Println("x is positive")
}
Choosing Between if and switch
- A
switchstatement indicates that a relationship exists between the values or comparisons in each case (similar comparisons, etc.).
goto
- Go forbids jumps that skip over variable declarations and jumps that go into an inner or parallel block.
- It makes the code more readable in rare situations, such as error handling.
- A real-world example of
goto:floatBitsinstrconv.