Basic types & strict typing

Station 1.3 14 min Interchange: type juggling & declare(strict_types=1) → int

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:02mismatched types untyped string and untyped int
Go doesn't do improv.

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, int and float64. Inferred numbers become int or float64.
  • 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 with math.Round if you need to.
  • Convert before dividing, not after. And string(65) is "A": use strconv.Itoa for digits.

1Type juggling

PHPjuggle.php
<?php
$stops = 3;
$minutes = 1.5;

echo $stops * $minutes; // 4.5
echo '5' + 3;           // 8
echo '5' . 3;           // "53"
GOjuggle.go
stops := 3     // int
minutes := 1.5 // float64

fmt.Println(stops * minutes)
// mismatched types int and float64

fmt.Println(float64(stops) * minutes) // 4.5

Mind the gap

Go never converts for you, not even int to float64. You say which type you want with a conversion, float64(stops), and the compiler checks the rest.

2strict_types, but at compile time

PHPstrict.php
<?php
declare(strict_types=1);

function wait(int $minutes): string { /* ... */ }

wait('5');   // TypeError at runtime
wait(5.0);   // TypeError at runtime
GOstrict.go
func wait(minutes int) string { /* ... */ }

wait("5") // compile error
wait(5.0) // fine: 5.0 is an untyped constant
wait(5.5) // compile error: truncated

Mind the gap

strict_types checks at runtime, one file at a time, and only at function boundaries. Go checks every expression, everywhere, before the program runs.

3Converting numbers

PHPconvert.php
<?php
$temp = 21.7;

echo (int) $temp;   // 21, truncated
echo round($temp);  // 22
echo intdiv(7, 2);  // 3
echo 7 / 2;         // 3.5
GOconvert.go
temp := 21.7

fmt.Println(int(temp))             // 21, truncated
fmt.Println(int(math.Round(temp))) // 22
fmt.Println(7 / 2)                 // 3
fmt.Println(float64(7) / 2)        // 3.5

Mind the gap

int(x) truncates toward zero, exactly like PHP's (int) cast. If you want rounding, round first: math.Round returns a float64, which you then convert.