Constants & iota

Station 1.7 13 min Interchange: const & define() & enum → const

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 Status field starts at 0, as you saw in 1.4. If 0 is OnTime, every departure nobody has set is on time. Make the first constant mean "not set yet", such as Unknown, or start at iota + 1 so that 0 matches nothing.
  • Inserting a constant renumbers everything below it. Add Boarding between OnTime and Delayed, and Delayed quietly 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

  • const values are worked out at compile time: numbers, strings and bools only. Runtime values need var.
  • type Status int makes a new named type that doesn't mix with plain ints by accident.
  • iota counts 0, 1, 2… down a const block, and lines without = repeat the expression. 1 << iota makes bit flags.
  • Go enums aren't closed: Status(42) compiles. Make the zero value mean "unset", and append new constants at the end.

1Constants

PHPconst.php
<?php
const MAX_CARRIAGES = 8;
define('LINE_NAME', 'Central');

final class Timetable
{
    public const FIRST_TRAIN = '05:30';
}
GOconst.go
const MaxCarriages = 8
const LineName = "Central"

const (
	FirstTrain = "05:30"
	LastTrain  = "00:30"
)

const now = time.Now() // compile error: not constant

Mind the gap

Go constants are worked out at compile time, so they can only be numbers, strings or bools built from other constants. No function calls, slices or structs.

2Enums

PHPenum.php
<?php
enum Status: int
{
    case OnTime = 1;
    case Delayed = 2;
    case Cancelled = 3;
}

Status::from(2);  // Status::Delayed
Status::from(42); // ValueError
GOenum.go
type Status int

const (
	Unknown Status = iota // 0
	OnTime                // 1
	Delayed               // 2
	Cancelled             // 3
)

s := Status(2) // Delayed
s = Status(42) // compiles: just a 42

Mind the gap

A PHP enum is a closed set, and from() rejects anything else. A Go "enum" is a named int type plus some constants. Any int converts to it, so the compiler won't stop Status(42).

3Bit flags

PHPflags.php
<?php
// E_ERROR is 1, E_WARNING is 2, E_PARSE is 4...
error_reporting(E_ALL & ~E_DEPRECATED);

$on = error_reporting() & E_WARNING;
GOflags.go
type Facility int

const (
	StepFree Facility = 1 << iota // 1
	Toilets                       // 2
	WiFi                          // 4
)

set := StepFree | WiFi   // 5
hasWiFi := set&WiFi != 0 // true
set = set &^ WiFi        // and-not: remove WiFi

Mind the gap

Same & and | as error_reporting. Go spells "and not" as one operator, &^, where PHP writes & ~. 1 << iota gives each flag its own bit.