17. Boolean logic in depth
Chapter 16 used &&, ||, and
! to join conditions. This chapter looks closer at what
they do, and at Go's strictest rule about conditions.
Only a bool goes in an
if
A condition must be a bool: true or false,
or something that gives one. Nothing else is allowed. A string, a
number, or an empty value does not count as true or false in Go.
Open exercises/17/01-not-a-bool.go. It has two
if statements, one with a string and one with the number
1. It does not compile:
# command-line-arguments
exercises\17\01-not-a-bool.go:13:5: non-boolean condition in if statement
exercises\17\01-not-a-bool.go:17:5: non-boolean condition in if statement
Non-boolean means "not a bool". Column 5 is where the condition starts. Replace the conditions with real comparisons, using the next example as a guide.
So write the question out. Asking whether a string has anything in it, or whether a number is not zero, takes a comparison:
name := "Keiko"
count := 3
if name != "" {
fmt.Println("Hello, " + name)
}
if count != 0 {
fmt.Println("The count is not zero.")
}Output:
Hello, Keiko
The count is not zero.
This takes a few more characters, and in return the condition says
exactly what it tests. name != "" cannot be misread. The
compiler also catches the mistake for you, before the program runs:
level := 1
if level {
fmt.Println("Has a level.")
}non-boolean condition in if statement
Comparisons, again
Each comparison from Chapter 16 gives a bool. Strings can be compared
too. < and > compare them in dictionary
order, with one catch: capital letters come before lower case ones.
fmt.Println("apple" == "apple") // true
fmt.Println("apple" != "banana") // true
fmt.Println("apple" < "banana") // true
fmt.Println("Zebra" < "apple") // trueBoth sides of a comparison must have the same type. Go does not turn a number into text for you, or text into a number:
score := 73
if score == "73" {
fmt.Println("Seventy-three.")
}invalid operation: score == "73" (mismatched types int and untyped string)
Convert first with strconv from Chapter 14, or compare
with the number 73.
&& and
|| give back a bool
&& and || take two bools and give
one bool. ! takes one bool and gives one bool. In full:
a |
b |
a && b |
a || b |
!a |
|---|---|---|---|---|
true |
true |
true |
true |
false |
true |
false |
false |
true |
false |
false |
true |
false |
true |
true |
false |
false |
false |
false |
true |
The output of exercises/17/02-bools.go tries a few of
these:
false
false
true
true
false
true
Can enter: true
Because the answer is a bool, you can store it in a variable with a
good name, then use the variable as the condition. A bool variable can
stand in an if alone, with no comparison:
level := 12
hasKey := true
canEnter := level >= 10 && hasKey
if canEnter {
fmt.Println("You can enter the dungeon.")
}
fmt.Println("Can enter:", canEnter)Output:
You can enter the dungeon.
Can enter: true
When && and || are mixed,
&& goes first, the way * goes before
+. Use parentheses when you want a different order, or when
they make the line clearer:
fmt.Println(true || false && false) // true
fmt.Println((true || false) && false) // falseA default value
A common job: use a value if there is one, and otherwise use a
fallback. || cannot do this, because it only works on
bools:
chosen := ""
move := chosen || "rock"
fmt.Println(move)invalid operation: operator || not defined on chosen (variable of type string)
Chapter 11 showed that an empty variable is not really empty. It
holds the zero value of its type, and for a string that is
"". So "nothing was entered" means the string equals
"", and an if supplies the fallback:
typedName := "" // nothing was entered
name := typedName
if name == "" {
name = "stranger"
}
fmt.Println("Hello, " + name) // Hello, strangerIf typedName had held a real value, name
would keep it instead. Start with the value, then replace it only when
it is missing. This is three lines long, and you will see it often.
This works when "" can never be a real answer. A number
is harder, since a 0 may be a real value or may be what
strconv.Atoi gave for bad text. Chapter 21 shows how to
tell those apart.
Open exercises/17/03-default.go. chosen is
set to "", so move falls back to
"rock". Set chosen to a real word and run it
again.
Short-circuit: the second side is skipped
&& and || stop as soon as the
answer is certain:
a && b: ifais false, the answer is false, andbis never worked out.a || b: ifais true, the answer is true, andbis never worked out.
This is called short-circuit evaluation. It lets the first test guard the second, which runs only after the first has passed:
total := 90
count := 0
// The average is only worth working out when count is not zero,
// so the count check guards the division.
if count > 0 && total/count > 20 {
fmt.Println("high average")
} else {
fmt.Println("no average to show")
}Output:
no average to show
Because count > 0 is false, Go never evaluates
total/count. It goes straight to the else. The
guard is needed, because dividing a whole number by zero stops the
program. Swap the two sides:
total := 90
count := 0
if total/count > 20 && count > 0 {
fmt.Println("high average")
}Now the division runs first, and the program crashes with:
panic: runtime error: integer divide by zero
This is the same kind of crash as the slice error in Chapter 12: the program compiled, then stopped while running.
|| guards in the same way. Here the division is reached
only when count is not zero:
total := 90
count := 0
if count == 0 || total/count < 20 {
fmt.Println("nothing special")
}Output:
nothing special
Open exercises/17/04-short-circuit.go. Run it, then swap
the two sides of && and run it again to see the
crash. Finally, put them back and set count to
3.
! flips a bool
! turns true into false and
false into true. It is handy for asking "is
this missing?" when you have a bool:
fmt.Println(!true) // false
fmt.Println(!false) // true
hasKey := false
if !hasKey {
fmt.Println("You need a key.")
}Output:
false
true
You need a key.
As with ||, it only works on bools. To ask the same
question about a string, compare it with "":
key := ""
if !key {
fmt.Println("You need a key.")
}invalid operation: operator ! not defined on key (variable of type string)
key := ""
if key == "" {
fmt.Println("You need a key.")
}Output:
You need a key.
Homework
Homework starters are in exercises/17/homework/. Each
one runs as it is and prints a placeholder line.
Problem 1 — Default colour
Open exercises/17/homework/01-default-colour.go.
fav holds a colour or "". Set
colour to fav, or to "blue" when
fav is empty, then print
Your colour is <colour>. Run it once with
fav := "" and once with a real colour.
Problem 2 — Say what you mean
Open exercises/17/homework/02-explicit-tests.go. Five
variables hold 0, "", false,
"hi", and 1. For each one, print the value and
the result of the test that asks whether it differs from its zero value,
such as count != 0. Use %q for the strings so
the empty one is visible. Which of the five needs no comparison?
Problem 3 — Guarded division
Open exercises/17/homework/03-guarded.go. Two numbers:
total and count (which might be
0). Using &&, when count
is greater than 0 and total/count is over 20,
print the average with two decimals, as a float64; otherwise print
no high average. Test it with count set to
0, to 3, and to 10.
Challenge — First value that exists
Open exercises/17/homework/04-first-value.go. Three
variables a, b, c each hold a
string or "". Using a switch with no value,
print the first one that is not empty, or none if all three
are empty. Try different combinations.
Stuck or finished? Open the homework solutions page.