6. Constants and `iota`

📖 Reading · 7 min
💡 Every code box below is live — edit it and hit Run.

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 iotaInfo, 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.