Logical Operators

How &&, || and ! combine true and false, and why the right-hand side sometimes never runs.

Syntaxa && b a || b !a // every operand must be a bool

Logical 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

Example · go
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 at

When 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.

Example · go
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: true

Cheap 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.

Example · go
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 path

The 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.

Example · go
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

  • Be the first to comment on this lesson.