Logical Operators
How &&, || and ! combine true and false, and why the right-hand side sometimes never runs.
a && b a || b !a // every operand must be a boolLogical operators combine true and false answers into one answer. && is "and": the result is true only when both sides are true. || is "or": the result is true when at least one side is true. ! is "not": it takes one boolean and flips it.
Booleans only
These three accept booleans and nothing else. A number does not stand in for true, and an empty string does not stand in for false. if count does not compile; write if count > 0. Go also has no and or or keyword, so && and || are the only spellings.
Go stops as soon as it knows
The left side is worked out first. For &&, a false left side settles the whole answer, so the right side is never worked out at all. For ||, a true left side settles it the same way. This is called short-circuit evaluation, and it is what makes a guard possible. len(s) > 0 && s[0] == 'h' never reaches into an empty string, because the length test fails first and stops the line. Order matters: the test that protects goes on the left.
Shorthand
if ok and if ok == true ask the same question. if !ok and if ok == false likewise. The short forms are the ones Go code uses; the long ones add a comparison that changes nothing.
Precedence
&& binds tighter than ||, so a || b && c means a || (b && c). Write the brackets yourself when you mean the other grouping.
Example
package main
import "fmt"
func main() {
// && needs both sides true. || needs at least one.
fmt.Println(true && false, true || false)
// ! flips a boolean. These two ask the same question.
age := 20
fmt.Println(!(age < 18), age >= 18)
// divisor is 0, so the left side of && is false and the
// division on the right is never worked out. Swap the two
// sides and the program panics instead.
divisor := 0
if divisor != 0 && 100 / divisor > 5 {
fmt.Println("more than 5")
} else {
fmt.Println("skipped the division")
}
// The same guard, protecting a read from a string. s[0] on an
// empty string would stop the program.
s := ""
if len(s) > 0 && s[0] == 'h' {
fmt.Println("starts with h")
} else {
fmt.Println("no first letter to look at")
}
}
// Output:
// false true
// true true
// skipped the division
// no first letter to look atWhen to use it
- Accept a submitted form only when every field passes its own check.
- Guard an index or a division so the risky operation never runs on bad input.
- Serve a cached answer when the cache is warm or the caller allows stale data.
- Skip an expensive lookup by putting the cheap test first in an &&.
More examples
Two ways to say the same no
Turning a condition inside out swaps && for || and negates each part. You will do this every time you rewrite a validation check as an early rejection, and getting it wrong by leaving && in place is a bug that passes most tests.
package main
import "fmt"
func main() {
name := "ada"
age := 15
// Both parts must hold for this to be accepted.
ok := len(name) > 0 && age >= 18
fmt.Println("accepted:", ok)
// These two lines ask the same question, because !(a && b)
// is the same as !a || !b.
fmt.Println("rejected:", !(len(name) > 0 && age >= 18))
fmt.Println("rejected:", len(name) == 0 || age < 18)
}
// Output:
// accepted: false
// rejected: true
// rejected: trueCheap test on the left
Short-circuit evaluation is a performance tool as well as a safety one. In a request handler, put the field check before the database lookup and the common case before the rare one, and the expensive work is skipped on most requests.
package main
import (
"fmt"
"strings"
)
func main() {
path := "/static/logo.png"
// The length test costs almost nothing, so it goes first and
// the string search only runs when it can matter.
if len(path) > 4 && strings.HasSuffix(path, ".png") {
fmt.Println("image")
}
// One false decides the whole chain, so the second call here
// is never made.
if strings.HasPrefix(path, "/api/") && strings.Contains(path, "v2") {
fmt.Println("api v2")
} else {
fmt.Println("not an api path")
}
}
// Output:
// image
// not an api pathThe condition you do not need to write
Worth reading once so that both forms look familiar, because other people's code uses both. Write the short form; the long one is also the form that invites the typo = for ==, which the compiler catches but only after it has wasted your time.
package main
import "fmt"
func main() {
verbose := true
// These two conditions are the same thing. The first is the
// form Go code uses; the second adds a comparison that does
// no work.
if verbose {
fmt.Println("short form")
}
if verbose == true {
fmt.Println("long form")
}
// And the negative pair, which are also the same.
if !verbose {
fmt.Println("never printed")
}
if verbose == false {
fmt.Println("never printed")
}
}
// Output:
// short form
// long form
Discussion