if, with a twist
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 ifis two words. There's noelseif.} else {stays on one line. Putelseon the next line and you getsyntax 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 8 | Nesting that without brackets is now a fatal error. |
| Go | What ternary? |
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 ifin 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 anif. if x := f(); x > 0 { … }runs a statement first.xonly exists in thatifand itselse.- Handle problems first and return early.