Basic types & strict typing
PHP will turn almost anything into almost anything else if you ask nicely, or even if you don't. '5' + 3 is 8. '5' . 3 is "53". declare(strict_types=1) claws some of that back, but only at function boundaries, only in files that opt in, and only at runtime.
Go is statically typed all the way down. Every variable and every expression has one type, the compiler knows it before the program runs, and nothing is ever converted for you.
| PHP, 09:00 | '5' + 3 is 8. Sure, why not. |
|---|---|
| PHP, 09:01 | '5' . 3 is "53". Also fine. |
| Go, 09:02 | mismatched types untyped string and untyped int |
The basic types
| Go | Holds | PHP |
|---|---|---|
bool |
true, false |
bool |
string |
text (UTF-8 bytes, see 1.5) | string |
int |
whole numbers, 64-bit on any server you'll deploy to | int |
float64 |
decimals | float |
That covers most code. Go also has sized integers, int8 to int64, unsigned ones like uint32, and float32. You'll meet them when a file format or API demands an exact size. Until then, use int and float64.
When Go infers a type, 3 becomes an int and 3.0 or 1.5 becomes a float64.
No conversions behind your back
PHP happily multiplies an int by a float. Go refuses:
stops := 3 // int minutes := 1.5 // float64 total := stops * minutes // mismatched types int and float64
int and float64 are different types, so you have to say which one you want. A conversion looks like a function call with the type's name: float64(stops) * minutes is 4.5. int(minutes) goes the other way.
It feels pedantic at first. Then you realise the compiler is doing what PHPStan does, on every line, without a config file.
Except for literals
There's one bit of flexibility. A number you type into the code, like 9 or 5.0, is an untyped constant. It doesn't pick a type until it has to, so it fits wherever it makes sense:
var temp float64 = 3 // fine: 3 becomes a float64 half := float64(stops) / 2 // 2 becomes a float64 too
The same goes for arguments: if wait takes an int, wait(5.0) is fine. The compiler only objects if the value can't fit: wait(5.5) and int(21.7) are both errors, because Go can see the .5 and .7 would be lost. That check only works for literals, though. Once the value is in a variable, conversion is allowed, and it can quietly throw data away.
Conversions that lose data
Float to int truncates. With temp := 21.7, int(temp) is 21, exactly like PHP's (int). It cuts toward zero, so -2.7 becomes -2. If you want rounding, math.Round returns the nearest whole number as a float64, which you then convert: int(math.Round(temp)).
Int division stays int. You met this in 0.6: 7 / 2 is 3. Which leads to Go's best-known conversion bug:
Here's the same sum done both ways, one step at a time:
%%{init: {"flowchart": {"nodeSpacing": 18}}}%%
flowchart TB
accTitle: Converting last compared with converting first
accDescr: With c = 21. Converting last: c times 9 is 189, divided by 5 in integer maths is 37 with the .8 thrown away, plus 32 is 69, and converting to float64 at the end gives 69.0. Converting first: float64 of c is 21.0, times 9 is 189.0, divided by 5 is 37.8, plus 32 is 69.8.
subgraph early ["Convert first"]
direction TB
b1["float64(c) = 21.0"] --> b2["* 9 = 189.0"] --> b3["/ 5 = 37.8"] --> b4["+ 32 = 69.8"]
end
subgraph late ["Convert last"]
direction TB
a1["c * 9 = 189"] --> a2["189 / 5 = 37<br/>.8 dropped"] --> a3["37 + 32 = 69"] --> a4["float64: 69.0"]
end
Mind the gap
Convert first, not last. With c := 21, float64(c*9/5 + 32) compiles, and gives 69, not 69.8. The maths inside the brackets runs first, in ints, and 189 / 5 has already dropped the .8 before float64 sees it. Convert the variable at the start, float64(c)*9/5 + 32, and the untyped 9, 5 and 32 follow it into float maths.
Overflow wraps. PHP turns an int that gets too big into a float. Go wraps it round instead: an int8 holding 127, plus one, is -128. With a 64-bit int, you won't hit the limit counting passengers. But it's worth knowing that nothing warns you.
Numbers aren't strings
string(65) compiles, and gives you "A", the character with code 65, not "65". go vet flags it, so you'll find out on Submit. To turn a number into its digits, use strconv.Itoa(65), or the fmt functions in 1.6. Going the other way, from "65" to 65, can fail, so it returns an error as well. That's lesson 7.1.
Symfony corner
Everything in a request arrives as a string. $request->query->get('page') is '2', not 2. Symfony's getInt('page') converts it for you and throws if it isn't a number, and Laravel's $request->integer('page') converts it too. Go hands you the string as well, and turning it into an int is your job, with an error to check when it isn't one. You'll do exactly that in the Unit 1 capstone, and Unit 8 covers requests properly.
Recap
- The everyday types are
bool,string,intandfloat64. Inferred numbers becomeintorfloat64. - Go never converts for you. Write the conversion:
float64(n),int(f). - Literals are untyped constants and fit wherever they make sense, as long as nothing is lost.
int(f)truncates toward zero. Round first withmath.Roundif you need to.- Convert before dividing, not after. And
string(65)is"A": usestrconv.Itoafor digits.