The toolchain

Station 0.3 10 min Interchange: php script.php & php -l → go run

PHP's tooling is a collection: php runs things, php -l checks syntax, Composer manages packages, PHPUnit tests, PHPStan or Psalm catch bugs, PHP-CS-Fixer argues about whitespace. Each comes from a different project, with its own config file and its own opinions.

Go ships one command, go, and it does nearly all of that. It's a Swiss Army knife, if the knife also came with a test runner and a formatter, and politely refused to cut anything unidiomatic.

The commands you'll use every day

You'd type in PHP In Go What it does
php hello.php go run . Compile to a temp binary and run it
(deploy the source) go build Compile to a real binary you keep
php -l file.php go build / go vet Syntax and type errors / suspicious code
vendor/bin/phpunit go test ./... Run tests (Unit 5)
composer require go get Add a dependency (lesson 0.4)
php-cs-fixer fix gofmt -w . Format code (lesson 0.5)
php.net go doc fmt.Println Docs, right in your terminal

The ./... you'll see everywhere means "this directory and everything below it". It's Go's version of a glob, and yes, it really is three dots.

go run vs go build

go run . feels like php hello.php: type it and see the output. Under the hood, though, it compiles your whole package to a temporary binary, runs it, then deletes it. go build does the same compile but leaves the binary on disk, named after your project, ready to copy to a server.

flowchart TB
  accTitle: go run compared with go build
  accDescr: Both commands compile the package. go run puts the binary in a temporary directory, runs it and deletes it. go build writes the binary to the current directory and stops, so you can run or deploy it later.
  src[Your .go files] --> c{{Compile the package}}
  c -->|go run .| tmp[Temp binary] --> run([Run it, then delete it])
  c -->|go build| bin[Binary on disk] --> ship([Run it, or copy it to a server])

Mind the gap

Run go run ., not go run main.go. Passing a file name compiles only that file, so anything defined in your other files vanishes and you get a baffling undefined: helper. The dot means "the whole package in this directory", which is almost always what you want.

The compiler is your php -l, times ten

php -l only checks syntax. The Go compiler checks syntax and types and that every name exists and that you used every import and variable. A lot of what PHPStan does at level 5 or so, Go does on every build, before you can even run the thing.

go vet: it compiles, but it's probably wrong

Some mistakes are perfectly legal Go. go vet looks for code that compiles but is almost certainly a bug. Its classic catch involves fmt.Printf and fmt.Sprintf, which work just like PHP's printf and sprintf: same %s and %d verbs, same idea. Here's a bug you've probably shipped at least once, arguments in the wrong order:

// mins is 3, dest is "Leeds"
fmt.Printf("%s departs in %d min\n", mins, dest)
// go vet: fmt.Printf format %s has arg mins of wrong type int
// go run: %!s(int=3) departs in %!d(string=Leeds) min

PHP would cheerfully print 3 departs in 0 min and let a passenger find out. Go's compiler lets it through too, but vet won't.

Here's the bit worth remembering: go test runs vet's most important checks before every test run, and refuses to run the tests if they fail. That's why, in this course, Run might "work" and print nonsense while Submit stops and shows you vet's complaints. The exercise is built around exactly that.

Recap

  • One go command: run, build, vet, test, get, fmt, doc.
  • go run . compiles and runs the whole package. Don't pass it a single file.
  • The compiler catches far more than php -l, and go vet catches code that compiles but is wrong. go test runs vet first.

1Running and checking code

PHPcommands-php.sh
php hello.php              # run a script
php -l hello.php           # syntax check only
php -S localhost:8000      # dev web server
vendor/bin/phpstan analyse # the deeper checks
GOcommands-go.sh
go run .                   # compile + run the package
go build                   # compile to a binary you keep
go vet ./...               # "compiles, but probably wrong"
go doc fmt.Println         # docs in your terminal

2Arguments in the wrong order

PHPprintf.php
<?php
$mins = 3;
$dest = 'Leeds';
printf("%s departs in %d min\n", $mins, $dest);
// 3 departs in 0 min   (no warning, no error)
GOprintf.go
// mins is 3, dest is "Leeds"
fmt.Printf("%s departs in %d min\n", mins, dest)
// go vet: fmt.Printf format %s has arg mins of wrong type int
// go run: %!s(int=3) departs in %!d(string=Leeds) min

Mind the gap

go test runs this vet check automatically, so in this course Submit will catch it even though Run prints something.