6. Constants and `iota`
Some values should never change once the program is compiled — a maximum,
a conversion factor, a fixed label. Those are constants, declared with
const instead of var.
const
package main
import "fmt"
func main() {
const pi = 3.14159
const greeting = "Welcome"
fmt.Println(greeting, pi)
}
A constant is fixed at compile time. Try to assign to it and the program
won't build. Constants can only hold values the compiler can compute up
front — numbers, strings, booleans — never something decided at runtime
(you can't make a const out of the current time, for example).
Why bother, when var works?
Two reasons. First, intent: const maxRetries = 3 tells a reader "this
never changes." Second, safety: the compiler enforces it, so nothing can
accidentally reassign it later.
Constants are also grouped nicely with a single const ( ... ) block:
package main
import "fmt"
func main() {
const (
appName = "BrevFeed"
maxRetries = 3
timeoutSec = 30
)
fmt.Printf("%s retries %d times, %ds timeout\n", appName, maxRetries, timeoutSec)
}
Untyped constants are flexible
A constant like const x = 10 has no fixed type until it's used. That lets
it slot into whichever numeric type the context needs — no conversion. This
is a small but real convenience:
package main
import "fmt"
func main() {
const factor = 100
var i int = factor // used as int
var f float64 = factor // same constant, used as float64
fmt.Println(i, f)
}
(A variable wouldn't do this — mixing an int variable with a float64
is an error. We cover conversions next lesson.)
iota: numbered constants for free
Often you want a set of related constants with increasing values — the days
of the week, log levels, states of a state machine. Typing 0, 1, 2, 3...
by hand is tedious and error-prone. iota does it for you.
Inside a const block, iota starts at 0 and increases by 1 on each
line:
package main
import "fmt"
func main() {
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
)
fmt.Println(Sunday, Monday, Tuesday, Wednesday)
}
You only write iota once; each following line repeats the pattern with the
next value. This is the idiomatic way to define enums in Go.
iota with expressions
iota is just a number, so you can compute with it. A classic use is powers
of two for sizes:
package main
import "fmt"
func main() {
const (
_ = iota // skip 0 with the blank identifier _
KB = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20
GB // 1 << 30
)
fmt.Println(KB, MB, GB)
}
_ is the blank identifier — a throwaway name for a value you want to
skip. Here it eats iota's 0 line so KB lands on iota == 1.
Your turn
Define three log-level constants with iota — Info, Warning, Error
(starting at 0) — and print exactly:
Info=0 Warning=1 Error=2
package main
import "fmt"
func main() {
const (
// define Info, Warning, Error with iota
)
fmt.Printf("Info=%d Warning=%d Error=%d\n", Info, Warning, Error)
}
package main
import "fmt"
func main() {
const (
Info = iota
Warning
Error
)
fmt.Printf("Info=%d Warning=%d Error=%d\n", Info, Warning, Error)
}
Next: converting between types when Go makes you spell it out.