Constants & iota
PHP has const, define(), class constants and, since 8.1, proper enums: a closed set of cases that from() checks for you. Go has constants too, and a trick called iota for numbering them. Together with a named type, that's how Go does enums. They're simpler than PHP's, and they guard less, which is the main thing this lesson is about.
Constants
const MaxCarriages = 8 const ( FirstTrain = "05:30" LastTrain = "00:30" )
A const block groups related constants, the way import (…) groups imports. Names use the same MixedCaps as everything else in Go, so no SHOUTY_CASE. (The capital letter isn't for shouting either. It controls visibility, which is lesson 1.11.)
Go works constants out at compile time, so a constant can only be a number, string or bool, built from literals and other constants. const started = time.Now() fails with time.Now() (value of struct type time.Time) is not constant, and there are no constant slices or structs. If the value is only known at runtime, it's a var.
Constants like these are untyped, exactly like the literals in 1.3. MaxCarriages slots into an int, a float64 or anything else numeric, wherever it makes sense.
Named types
Before enums, one new piece of syntax:
type Status int
This creates a new type called Status, stored as an int. It's not an alias. A plain int variable won't go into a Status without a conversion, Status(n), so you can't pass a passenger count where a status was expected by accident. Untyped constants still fit straight in, as always.
iota
Inside a const block, iota is a counter. It's 0 on the first line, 1 on the next, and so on. And when a line has no = at all, Go repeats the previous line's type and expression. Together, those two rules give you an enum in five lines:
const ( Unknown Status = iota // 0 OnTime // 1 Delayed // 2 Cancelled // 3 )
Because the expression is repeated, you can do maths with it. iota + 1 starts counting at 1, _ = iota skips a value, and 1 << iota gives you bit flags. << shifts left, as in PHP, so 1 << 0 is 1, 1 << 1 is 2, 1 << 2 is 4, and each flag gets its own bit:
const ( StepFree Facility = 1 << iota // 1 Toilets // 2 WiFi // 4 )
These combine exactly like PHP's E_ALL & ~E_DEPRECATED: | joins flags, set&WiFi != 0 tests one, and &^ ("and not") removes one, where PHP writes & ~.
That test hides a precedence difference. In PHP, $set & TOILETS != 0 means $set & (TOILETS != 0), which is $set & 1, so a station with no toilets but step-free access passes the check. That's why PHP code wraps it in brackets. Go's & binds tighter than !=, the way * binds tighter than +, so set&WiFi != 0 does what it looks like. gofmt's spacing shows the grouping.
Where Go enums are weaker
Mind the gap
A Go enum is not a closed set. PHP's Status::from(42) throws a ValueError. Go's Status(42) compiles and runs, and you now have a Status that matches none of your constants. If values come from outside (a form, a database, JSON), check them yourself. Lesson 3.8 shows the usual validation pattern.
Two more things follow from "it's just an int":
- The zero value is the first constant. A struct with a
Statusfield starts at0, as you saw in 1.4. If0isOnTime, every departure nobody has set is on time. Make the first constant mean "not set yet", such asUnknown, or start atiota + 1so that0matches nothing. - Inserting a constant renumbers everything below it. Add
BoardingbetweenOnTimeandDelayed, andDelayedquietly changes from 2 to 3. If those numbers are stored anywhere, like a database column or a queued job, add new constants at the end, or write the values out explicitly.
Laravel corner
Laravel closes the set for you at the edges. An enum cast ('status' => Status::class) calls Status::from() when it reads the column, and Rule::enum(Status::class) rejects bad form input before your code sees it. Go has neither, so the check is a function you write and call. Lesson 3.8 shows the pattern.
Printing one shows the number too: fmt.Println(Delayed) prints 2. Go has a standard way to give them names, a String() method, and that's lesson 3.8 as well.
(iota is the smallest letter of the Greek alphabet, hence "not one iota". Fitting, for a feature that's mostly a + 1.)
Recap
constvalues are worked out at compile time: numbers, strings and bools only. Runtime values needvar.type Status intmakes a new named type that doesn't mix with plain ints by accident.iotacounts 0, 1, 2… down aconstblock, and lines without=repeat the expression.1 << iotamakes bit flags.- Go enums aren't closed:
Status(42)compiles. Make the zero value mean "unset", and append new constants at the end.