Zero values, not null

Station 1.4 12 min Interchange: null & ?? & isset() → zero values

In PHP, a value you never set is null. Or it's undefined, PHP throws a warning, and then it's null. Half your career has been ??, isset() and ?->, and that's fine. It's how the language works.

Go takes a different line: every type has a zero value, and anything you declare without assigning starts there. Not null. Not undefined. Just the boring, usable default for that type.

Type Zero value Roughly in PHP
int, float64 0 (int) null
string "" (string) null
bool false (bool) null
struct every field at its zero value a DTO with defaults everywhere
pointer, slice, map, interface nil null, finally

A quick word on that struct row, since structs turn up in this lesson's examples. A struct is a named bundle of typed fields: think of a PHP class with only public typed properties and no methods, or an associative array whose keys are fixed and checked by the compiler.

type Config struct {
	Host  string
	Port  int
	Debug bool
}

Config{} builds one with every field left at its zero value, Config{Port: 9000} sets just the fields you name, and cfg.Port reads a field (no ->, just a dot). That's all you need for now. Structs get proper lessons of their own in Units 2 and 3.

So var port int, the no-value form from 1.1, is 0 right now, with no warning. And Config{} is a perfectly valid config, with an empty host, port 0 and debug off. You can pass it around, compare it and print it.

Wait, Go has nil?

It does, but only for things that point at something: pointers, slices, maps, channels, functions and interfaces. You'll meet each of them in later units. A string can never be nil, and neither can an int. The compiler won't let you write if name == nil, which wipes out a whole category of "null where I expected a string" bugs.

Mind the gap

Zero can be a real value. Port 0, a discount of 0, false meaning "explicitly off". Go can't tell "never set" from "set to zero". When that difference matters, reach for a pointer (*int, where nil means unset, covered in 3.3), or the comma-ok idiom for maps (2.4).

PHP typed properties, for comparison

PHP 7.4+ got closer, then swerved. A typed property with no default isn't null, it's uninitialised, and reading it throws an Error. Go would just hand you 0. Check the Interchange tab: both versions are side by side.

Laravel corner

You're used to config('database.port', 5432), with the default supplied at read time, every time. Go code usually applies defaults once, when the config struct is built, and passes the finished struct around. That's exactly what the exercise asks you to write.

Recap

  • Declared but unassigned means zero value: 0, "", false, or nil for reference-y types.
  • string and int can't be nil. No ??; you compare against the zero value.
  • When zero is a legitimate value, use a pointer or comma-ok to spot "unset".

1Missing values

PHPmissing.php
<?php
$cfg = ['host' => 'db.local'];

$port = $cfg['port'] ?? 5432; // null → 5432
$debug = $cfg['debug'] ?? false;

echo $undefined; // Warning, then null
GOmissing.go
var port int    // 0
var host string // ""
var debug bool  // false

if port == 0 {
	port = 5432
}

fmt.Println(undefined) // compile error

Mind the gap

Using a variable that doesn't exist is a compile error in Go, not a runtime warning. You find out before deploy.

2Typed properties vs struct fields

PHPtyped.php
<?php
final class Config
{
    public string $host;
    public int $port;
}

$c = new Config();
echo $c->port;
// Error: must not be accessed
// before initialization
GOtyped.go
type Config struct {
	Host string
	Port int
}

c := Config{}
fmt.Println(c.Port) // 0
fmt.Printf("%+v\n", c) // %+v: lesson 1.6
// {Host: Port:0}