Constants

A constant is a name for a value that never changes, like the number of seconds in a minute or the name of your program. In this lesson you’ll declare constants with const, learn the difference between typed and untyped constants, build constants from expressions, and use iota to number a list of related values.

This lesson builds on Variables, types and pointers, so you should know var, := and the basic types.

Declaring a constant

You declare a constant like a variable, but with const instead of var, and you must give it a value right away:

package main

import "fmt"

const appName = "Greeter"
const maxUsers = 100

func main() {
	const greeting = "Hello"
	fmt.Println(greeting, "from", appName)
	fmt.Println("Room for", maxUsers, "users")
}Code language: Go (go)
Hello from Greeter
Room for 100 usersCode language: plaintext (plaintext)

Constants can live at package level (appName, maxUsers) or inside a function (greeting). There is no := for constants. And unlike variables, an unused constant is not an error.

The point of a constant is that it can’t change. Try it, and the compiler stops you:

package main

import "fmt"

const maxUsers = 100

func main() {
	maxUsers = 200
	fmt.Println(maxUsers)
}Code language: Go (go)
./main.go:8:2: cannot assign to maxUsers (neither addressable nor a map index expression)Code language: plaintext (plaintext)

The message is a bit cryptic. “Not addressable” means there’s no spot in memory you could write to: a constant isn’t stored like a variable, the compiler pastes its value wherever you use it. That’s also why you can’t take the address of a constant with &.

Grouping constants

Related constants usually go in a group with parentheses, just like var. A constant can be built from other constants:

package main

import "fmt"

const (
	secondsPerMinute = 60
	secondsPerHour   = 60 * secondsPerMinute
	secondsPerDay    = 24 * secondsPerHour
)

func main() {
	fmt.Println(secondsPerDay)
}Code language: Go (go)
86400Code language: plaintext (plaintext)

Constant expressions

24 * secondsPerHour is a constant expression: it only uses constants and operators, so the compiler calculates it while building your program. Nothing is computed when the program runs.

That also means a constant can only hold values the compiler can know in advance: numbers, strings, booleans (and characters, which are numbers in Go). A few built-in functions work too, like len of a constant string. A value that’s only known when the program runs can’t be a constant:

package main

import (
	"fmt"
	"time"
)

const start = time.Now()

func main() {
	fmt.Println(start)
}Code language: Go (go)
./main.go:8:15: time.Now() (value of struct type time.Time) is not constantCode language: plaintext (plaintext)

The current time depends on when you run the program, so it has to be a variable: var start = time.Now() works fine. Go has no constant slices, maps or structs either; you’ll use variables for those.

Untyped constants

Here’s where constants differ most from variables. When you write const timeout = 5 without a type, you get an untyped constant. It’s just “the number 5”, and it takes on whatever numeric type the place you use it needs:

package main

import "fmt"

const timeout = 5

func main() {
	var seconds float64 = timeout
	var retries int = timeout
	var small int8 = timeout
	fmt.Println(seconds, retries, small)

	fmt.Printf("%T\n", timeout)
}Code language: Go (go)
5 5 5
intCode language: plaintext (plaintext)

Remember from the previous lesson that Go never mixes types without a conversion. With an untyped constant, you don’t need one: the same timeout works as a float64, an int and an int8. Only when nothing tells Go which type to use, like when you hand it to Printf, does it fall back to a default type: int for whole numbers, float64 for numbers with a decimal point, string for text.

This is why you can write math.Pi * radius with a float64 radius, or assign math.Pi to a float32: math.Pi is an untyped constant.

Untyped constants are very precise

The compiler calculates untyped constants with much more precision than any Go type has. A constant can even be bigger than any integer type, as long as the value you finally use fits:

package main

import "fmt"

const huge = 1 << 100

func main() {
	fmt.Println(huge >> 98)
}Code language: Go (go)
4Code language: plaintext (plaintext)

1 << 100 is 1 shifted left by 100 bits, or 2 to the power 100, far too big for an int64. Shifted back down by 98 bits it’s 4, which fits fine. Try to print huge itself and the compiler tells you it doesn’t fit:

package main

import "fmt"

const huge = 1 << 100

func main() {
	fmt.Println(huge)
}Code language: Go (go)
./main.go:8:14: cannot use huge (untyped int constant 1267650600228229401496703205376) as int value in argument to fmt.Println (overflows)Code language: plaintext (plaintext)

The same check protects you from smaller mistakes, such as var small int8 = 200 (an int8 only goes up to 127). You get a compile error instead of a silently wrong number.

Watch out for integer division

An untyped whole-number constant divided by another whole number is integer division, just like with int variables:

package main

import "fmt"

func main() {
	const total = 10
	fmt.Println(total/4, total/4.0)
}Code language: Go (go)
2 2.5Code language: plaintext (plaintext)

Write 4.0 (or define the constant as 10.0) when you want the fraction.

Typed constants

You can give a constant a type, just like a variable. Then it behaves like a value of that type and loses its flexibility:

package main

import "fmt"

const timeout int = 5

func main() {
	var seconds float64 = timeout
	fmt.Println(seconds)
}Code language: Go (go)
./main.go:8:24: cannot use timeout (constant 5 of type int) as float64 value in variable declarationCode language: plaintext (plaintext)

So which should you use? Leave constants untyped unless you have a reason to pin the type. The best reason is the next topic: giving a set of constants their own type, so they can’t be mixed up with ordinary numbers.

iota: numbering a list of constants

Often you need a handful of related values where the exact numbers don’t matter, only that they’re different: days of the week, log levels, the states of an order. Many languages have an enum keyword for this. Go uses a constant group and iota.

Inside a const group, iota is 0 on the first line, 1 on the second, and so on. Combine that with another rule, that a line without a value repeats the expression from the line above, and you get this:

package main

import "fmt"

type Weekday int

const (
	Sunday Weekday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Saturday
)

func main() {
	fmt.Println(Sunday, Monday, Saturday)
	fmt.Printf("%T\n", Monday)

	day := Wednesday
	fmt.Println(day == Wednesday)
}Code language: Go (go)
0 1 6
main.Weekday
trueCode language: plaintext (plaintext)

Each line repeats Weekday = iota, and iota goes up by one per line, so Sunday is 0 and Saturday is 6.

The line type Weekday int creates a new type named Weekday that stores an int. You’ll learn more about defining your own types in Structs. Here it buys you safety: a Weekday is not an int, so the compiler stops you from comparing a day with some unrelated number by accident:

package main

import "fmt"

type Weekday int

const Monday Weekday = 1

func main() {
	n := 1
	fmt.Println(Monday == n)
}Code language: Go (go)
./main.go:11:24: invalid operation: Monday == n (mismatched types Weekday and int)Code language: plaintext (plaintext)

Printing a Weekday shows a number, not “Monday”. To print names, you give the type a String method; that’s covered in Methods.

Starting at 1

Because a new variable of the type starts at its zero value, 0, it often helps to keep 0 for “not set”. Start your list at 1 with iota + 1:

package main

import "fmt"

type Size int

const (
	Small Size = iota + 1
	Medium
	Large
)

func main() {
	var s Size
	fmt.Println(s, Small, Medium, Large)
}Code language: Go (go)
0 1 2 3Code language: plaintext (plaintext)

Now an s that nobody set is 0, which isn’t a valid size, so you can tell it apart from a deliberate Small.

Skipping values and using expressions

iota can be part of any constant expression, and a blank identifier _ skips a value. This classic example skips 0 and computes powers of 1024:

package main

import "fmt"

const (
	_  = iota
	KB = 1 << (10 * iota)
	MB
	GB
	TB
)

func main() {
	fmt.Println(KB, MB, GB, TB)
}Code language: Go (go)
1024 1048576 1073741824 1099511627776Code language: plaintext (plaintext)

On the KB line iota is 1, so it’s 1 << 10, which is 1024. MB repeats the expression with iota 2, giving 1 << 20, and so on. The same trick with 1 << iota gives you 1, 2, 4, 8, …, which is how Go code defines bit flags that can be combined.

The numbers behind iota depend on the order of the lines. If you save them in a file or database, or send them over the network, inserting a new constant in the middle changes the meaning of the stored numbers. Add new values at the end, or write the numbers out explicitly.

When to use constants

  • For values that are fixed by definition: secondsPerDay, a maximum length, a default port.
  • To give a “magic number” a name. if retries > maxRetries says more than if retries > 3, and you change it in one place.
  • For a fixed set of options, with their own type and iota.

Use a variable instead when the value comes from outside (a config file, the user, the clock), when it’s a slice, map or struct, or when the program needs to change it.

Practice

1. Define constants for the seconds in a minute, an hour, a day and a week, each built from the one before, and print the number of seconds in a week.

Show a solution
package main

import "fmt"

const (
	secondsPerMinute = 60
	secondsPerHour   = 60 * secondsPerMinute
	secondsPerDay    = 24 * secondsPerHour
	secondsPerWeek   = 7 * secondsPerDay
)

func main() {
	fmt.Println(secondsPerWeek)
}Code language: Go (go)
604800Code language: plaintext (plaintext)

2. Create a type Level with the constants Debug, Info, Warn and Error, numbered from 1 with iota. Store Warn in a variable and print it, and print whether it equals Error.

Show a solution
package main

import "fmt"

type Level int

const (
	Debug Level = iota + 1
	Info
	Warn
	Error
)

func main() {
	level := Warn
	fmt.Println("level:", level)
	fmt.Println("is error:", level == Error)
}Code language: Go (go)
level: 3
is error: falseCode language: plaintext (plaintext)

Key takeaways

  • const declares a value that can’t change; the compiler rejects any assignment to it.
  • Constants are computed at compile time, so they can only hold numbers, strings and booleans.
  • Untyped constants adapt to the type they’re used as and are checked for overflow; typed constants behave like their type.
  • Use a named type plus iota for enumerations, and consider starting at 1 so 0 means “not set”.
  • Don’t store raw iota values if you might reorder or insert constants later.