This is a creation in Article, where the information may have evolved or changed.
Note: The author of this article is Katrina Owen, the original address is iota:elegant Constants in Golang
Some concepts have names, and sometimes we focus on those names, even (especially) in our code.
const ( CCVisa = "Visa" CCMasterCard = "MasterCard" CCAmericanExpress = "American Express")
At other times, we just focus on being able to differentiate one thing from the other. Sometimes, there are times when something has no intrinsic meaning. For example, we store products in a database table, and we may not want to store their classifications as strings. We are not concerned about how this classification is named, moreover, the name has been changing in the market.
We are only concerned with how they are differentiated from each other.
const ( CategoryBooks = 0 CategoryHealth = 1 CategoryClothing = 2)
Using 0, 1, and 2 instead, we can also choose 17, 43, and 61. These values are arbitrary.
Constants are important, but they are difficult to infer and difficult to maintain. In some languages like Ruby developers usually just avoid them. In Go, constants have many subtleties. When used well, it makes the code very elegant and easy to maintain.
Self-growth
In Golang, a handy idiom is the use of identifiers iota
, which simplifies the definition of constants for growth numbers, giving the same values to the exact classification.
const ( CategoryBooks = iota // 0 CategoryHealth // 1 CategoryClothing // 2)
Custom Types
Self-growing constants often contain a custom type that allows you to rely on the compiler.
type Stereotype intconst ( TypicalNoob Stereotype = iota // 0 TypicalHipster // 1 TypicalUnixWizard // 2 TypicalStartupFounder // 3)
If a function takes int
its arguments instead of Stereotype
, if you pass one to it Stereotype
, it will have problems during the compiler period.
func CountAllTheThings(i int) string { return fmt.Sprintf("there are %d things", i)}func main() { n := TypicalHipster fmt.Println(CountAllTheThings(n))}// output:// cannot use TypicalHipster (type Stereotype) as type int in argument to CountAllTheThings
The opposite is also true. Given a function Stereotype
as an argument, you can't pass it on int
.
func SoSayethThe(character Stereotype) string { var s string switch character { case TypicalNoob: s = "I'm a confused ninja rockstar." case TypicalHipster: s = "Everything was better we programmed uphill and barefoot in the snow on the SUTX 5918" case TypicalUnixWizard: s = "sudo grep awk sed %!#?!!1!" case TypicalStartupFounder: s = "exploit compelling convergence to syndicate geo-targeted solutions" } return s}func main() { i := 2 fmt.Println(SoSayethThe(i))}// output:// cannot use i (type int) as type Stereotype in argument to SoSayethThe
This is a dramatic twist, albeit so. You can pass a numeric constant, and then it will work.
func main() { fmt.Println(SoSayethThe(0))}// output:// I'm a confused ninja rockstar.
This is because constants are weakly typed in Go until it is used in a strict context.
Skipping Values
Imagine that you are dealing with consumer audio output. The audio may not have any output whatsoever, or it may be mono, stereo, or surround sound.
This may have some potential logic definitions without any output of 0, mono as 1, stereo 2, and the value is provided by the number of channels.
So what value do you give Dolby 5.1 surround stereo.
On the one hand, it has 6 channel outputs, but on the other hand, only 5 channels are full bandwidth channels (hence the 5.1 designation-which .1
represents the low-frequency effect channel).
Anyway, we don't want to add to 3 simply.
We can use the underscore to skip the unwanted values.
type AudioOutput intconst ( OutMute AudioOutput = iota // 0 OutMono // 1 OutStereo // 2 _ _ OutSurround // 5)
An expression
iota
You can do more, not just increment. More precisely, it is iota
always used for increment, but it can be used to store the resulting value in an expression, in a constant.
Here we create a constant for the bitmask.
type Allergen intconst ( IgEggs Allergen = 1 << iota // 1 << 0 which is 00000001 IgChocolate // 1 << 1 which is 00000010 IgNuts // 1 << 2 which is 00000100 IgStrawberries // 1 << 3 which is 00001000 IgShellfish // 1 << 4 which is 00010000)
The job is because when you const
have only one noon line in a group, it will use the growth to iota
get the previous expression and use it again. In the Go language spec, this is known as the implicit repetition of the last non-empty expression list.
If you are allergic to eggs, chocolate and seafood, flip these bits to the "on" position (mapping bits from left to right). Then you will get a bit value 00010011
that corresponds to the decimal 19.
fmt.Println(IgEggs | IgChocolate | IgShellfish)// output:// 19
This is an example of a very good definition of the order of magnitude in effective Go:
type ByteSize float64const ( _ = iota // ignore first value by assigning to blank identifier KB ByteSize = 1 << (10 * iota) // 1 << (10*1) MB // 1 << (10*2) GB // 1 << (10*3) TB // 1 << (10*4) PB // 1 << (10*5) EB // 1 << (10*6) ZB // 1 << (10*7) YB // 1 << (10*8))
Today I learned that after Zettabyte is yottabyte.
But wait, there's more.
What happens when you define two constants in a row?
What is the value of Banana? 2 or 3? What is the value of Durian?
const ( Apple, Banana = iota + 1, iota + 2 Cherimoya, Durian Elderberry, Fig)
iota
Grows on the next line instead of immediately getting its reference.
// Apple: 1// Banana: 2// Cherimoya: 2// Durian: 3// Elderberry: 3// Fig: 4
This is messed up, because now your constants have the same value.
Therefore, the right
In Go, there are a lot of things to say about constants, and you should read Rob Pike's article on the Golang blog.