Constants and iota
A constant is a value fixed when the program is compiled: a tax rate, a maximum size, the name of an environment variable. Go's constants have a few special powers — they can be untyped, they're calculated with exact precision, and the iota counter makes it easy to define a numbered set of related values, Go's equivalent of an enum.
Declaring constants
package main import "fmt" const appName = "Shopfront" const ( maxItems = 50 vatRate = 0.2 welcome = "Welcome to " + appName // built from other constants ) func main() { const discount = 5 // constants can be local too price := 40.0 fmt.Println(welcome) fmt.Println(maxItems, price*(1+vatRate)-discount) }
Welcome to Shopfront 50 43
Constants can be numbers, strings, booleans or runes — not slices, maps or structs. Their value must be computable at compile time, so const now = time.Now() is an error. A constant can't be changed: assigning to one doesn't compile.
Typed and untyped constants
A constant declared without a type, such as const limit = 10, is untyped. It takes on whatever type its context needs, which is why one constant can be used with several numeric types:
package main import "fmt" const limit = 10 // untyped const typedLimit int = 10 // typed: always an int func main() { var a int8 = limit var b float64 = limit var c uint = limit fmt.Println(a, b, c) var d float64 = float64(typedLimit) // a typed constant needs a conversion fmt.Println(d) }
10 10 10 10
An untyped constant still gets a default type when there's no context, for example in x := limit: int for whole numbers, float64 for decimals, rune for characters and string for text.
Exact arithmetic
The compiler calculates constant expressions with very high precision, and only rounds when a constant is finally given a concrete type. Intermediate results can be far larger than any integer type:
package main import "fmt" const huge = 1 << 100 // far beyond int64 const backDown = huge >> 98 // 4: fits comfortably func main() { fmt.Println(backDown) fmt.Println(0.1+0.2 == 0.3) // exact at compile time }
4 true
Only the final value has to fit its type. fmt.Println(huge) would not compile, because huge overflows int.
iota
Inside a const ( … ) group, the identifier iota stands for the line's position: 0 for the first constant, 1 for the second, and so on. If a constant has no expression, it repeats the previous one — with the new iota value. Together these make enumerations easy:
package main import "fmt" type Weekday int const ( Sunday Weekday = iota // 0 Monday // 1 Tuesday // 2 Wednesday Thursday Friday Saturday ) func main() { fmt.Println(Sunday, Wednesday, Saturday) today := Friday fmt.Println(today == Friday, today > Monday) }
0 3 6 true true
Giving the constants their own type, Weekday, means a function that takes a Weekday documents what it expects. In lesson 42 you'll give the type a String method so it prints as "Friday" instead of 5.
iota patterns
package main import "fmt" type Size int const ( _ = iota // skip zero, so the zero value means "not set" Small Size = iota Medium Large ) const ( _ = iota KB = 1 << (10 * iota) // 1 << 10 MB // 1 << 20 GB // 1 << 30 ) type Permission uint8 const ( Read Permission = 1 << iota // 1 Write // 2 Execute // 4 ) func main() { fmt.Println(Small, Medium, Large) fmt.Println(KB, MB, GB) p := Read | Write fmt.Println(p, p&Write != 0, p&Execute != 0) }
1 2 3 1024 1048576 1073741824 3 true false
Starting enums at 1 (by skipping 0 with _) is a common trick, so that a forgotten field — which defaults to zero — can be told apart from a real choice. The 1 << iota pattern makes bit flags that can be combined with | and tested with & (lesson 11).
Try it yourself
Define order statuses with iota and print one. Then add a new status in the middle and see the numbers shift.
status code: 3 done? false