if, with a twist

Station 1.8 11 min Interchange: if / elseif & ? : & truthiness → if

You'd think if would be the one thing that carries over unchanged. Mostly it does. But Go drops three things you use every day, the brackets, truthiness and the ternary, and adds one you'll come to like.

The shape

if delay > 30 {
	label = "Very late"
} else if delay > 0 {
	label = "Late"
} else {
	label = "On time"
}
  • No brackets round the condition. gofmt removes them if you add them out of habit.
  • Braces are always required, even for a one-line body. There's no if ($x) return;.
  • else if is two words. There's no elseif.
  • } else { stays on one line. Put else on the next line and you get syntax error: unexpected keyword else, for the same semicolon reason as the braces in 0.2.

&&, || and ! work as you'd expect. There's no and/or keyword version, and no === either. Go's == is already strict, because both sides must be the same type: comparing an int with a string doesn't compile.

Conditions must be bools

In PHP, almost anything can be a condition. if ($stops) means "the array isn't empty", if (!$delay) means "zero or null", and if ($name) means "not empty, and also not the string '0'", a rule that has caught out every PHP dev at least once.

Go only accepts a bool. if stops is non-boolean condition in if statement, and !delay is operator ! not defined on delay (variable of type int). You ask the real question instead: len(stops) > 0, delay == 0, name != "". Because there's no null to worry about (1.4), comparing with the zero value is nearly always what you want.

No ternary

PHP 5$a ? $b : $c ? $d : $e groups left to right. Surprise!
PHP 8Nesting that without brackets is now a fatal error.
GoWhat ternary?
Go skipped straight to the ending.

Go has no ? :, no ?: and no ??. The Go way is to set the default, then overwrite it:

label := "On time"
if delay > 0 {
	label = "Late"
}

It's three lines longer than the PHP. The Go team's view is that ternaries get nested until nobody can read them, and that a plain if is always clear. If you find yourself writing the same pattern a lot, make it a small function.

if with a short statement

This is the twist. An if can run a short statement before its condition, separated by a semicolon:

if late := len(delayed); late > 0 {
	fmt.Printf("%d trains delayed\n", late)
}

late is declared, then the condition is checked. Like the block scope in 1.1, late belongs to the if. You can use it in the if and in any else branches, and it's gone afterwards.

It looks like a party trick now, but you'll write it constantly. Two very common uses are coming up: if err := save(); err != nil { … } for errors (Unit 4) and if v, ok := m[key]; ok { … } for map lookups (2.4). Both keep a variable that only matters to one check out of the rest of the function.

Tip

Return early. Go code checks for the problem cases first, returns, and keeps the main path running down the left edge with no else. When an if ends in return, you don't need an else after it, and linters will tell you so. If you've read Laravel code full of guard clauses, this will feel familiar.

Recap

  • No brackets, braces always, else if in two words, and } else { on one line.
  • Conditions must be bool: no truthiness. Compare against the zero value yourself.
  • No ternary, ?: or ??. Set a default, then overwrite it in an if.
  • if x := f(); x > 0 { … } runs a statement first. x only exists in that if and its else.
  • Handle problems first and return early.

1if, else if, else

PHPbasic.php
<?php
if ($delay > 30) {
    $label = 'Very late';
} elseif ($delay > 0) {
    $label = 'Late';
} else {
    $label = 'On time';
}
GObasic.go
if delay > 30 {
	label = "Very late"
} else if delay > 0 {
	label = "Late"
} else {
	label = "On time"
}

Mind the gap

No brackets round the condition, braces always required, and else if is two words. } else { must stay on one line, for the same semicolon reason as 0.2's braces.

2No ternary

PHPternary.php
<?php
$label = $delay > 0 ? 'Late' : 'On time';

$name = $user->nickname ?: $user->name;
GOternary.go
label := "On time"
if delay > 0 {
	label = "Late"
}

name := user.Nickname
if name == "" {
	name = user.Name
}

Mind the gap

Go has no ? :, no ?: and no ??. Set the default, then overwrite it in an if. It's longer, and on purpose: nobody can nest one.

3No truthiness

PHPtruthy.php
<?php
if ($stops) { /* non-empty array */ }
if (!$delay) { /* 0, or null */ }
if ($name) { /* not '', and not '0'! */ }
GOtruthy.go
if len(stops) > 0 { /* ... */ }
if delay == 0 { /* ... */ }
if name != "" { /* ... */ }

Mind the gap

A Go condition must be a bool. You compare against the zero value yourself, which also means "0" is never accidentally false.

4if with a short statement

PHPinit.php
<?php
if (($late = count($delayed)) > 0) {
    echo "{$late} trains delayed";
}

echo $late; // still here
GOinit.go
if late := len(delayed); late > 0 {
	fmt.Printf("%d trains delayed\n", late)
}

fmt.Println(late) // undefined: late

Mind the gap

The statement before the ; runs first, and the variable it declares only exists inside the if and its else branches.