Constants are declared using theconstkeyword; variables can be declared using thevar keyword, or using the short variable declaration syntax. Go can infer the type of the declared type, although it is legal to specify it if we wish to or need to—for example, to specify a type that is different from the type Go would normally infer. Here are some example declarations:
const limit = 512 // constant; type-compatible with any number const top uint16 = 1421 // constant; type: uint16
start := -19 // variable; inferred type: int end := int64(9876543210) // variable; type: int64 var i int // variable; value 0; type: int var debug = false // variable; inferred type: bool checkResults := true // variable; inferred type: bool stepSize := 1.5 // variable; inferred type: float64 acronym := "FOSS" // variable; inferred type: string
For integer literals Go infers typeint, for floating-point literals Go infers type float64, and for complex literals Go infers typecomplex128(the numbers in their names refer to how many bits they occupy). The normal practice is to leave types unspecified unless we want to use a specific type that Go won’t infer; we will discuss this further in §2.3,➤ 57. Typed numeric constants (e.g.,top) can only be used in expressions with other numbers of the same type (unless converted).
Untyped numeric constants can be used in expressions with numbers of any built-in type, (e.g.,limitcan be used in an expression with integers or in one with floating-point numbers).
The variableiwas not given any explicit value. This is perfectly safe in Go since Go always assigns variables their type’s zero value if no other value is specified.
ptg7913109 This means that every numeric variable is guaranteed to be zero and every
string to be empty—unless we specify otherwise. This ensures that Go programs don’t suffer from the problems of uninitialized garbage values that afflict some other languages.
2.1.1.1. Enumerations
Rather than repeat theconstkeyword when we want to set multiple constants, we can group together several constant declarations using theconst keyword just once. (We used the same grouping syntax when importing packages in Chapter 1; the syntax can also be used to group variables declared withvar.) For those cases where we just want constants to have distinct values and don’t really care what those values are, we can use Go’s somewhat bare-bones enumeration support.
These three code snippets all achieve exactly the same thing. The way a group of consts works is that the first one is set to its zero value unless explicitly set (either to a value or toiota), and the second and subsequent ones are set to their predecessor’s value—or to iota if their predecessor’s value is iota. And each subsequentiotavalue is one more than the previous one.
More formally, the iota predefined identifier represents successive untyped integer constants. Its value is reset to zero whenever the keywordconstoccurs (so every time a new const group is defined), and increments by one for each constant declaration. So in the right-hand code snippet all the constants are set toiota(implicitly for theMagentaandYellowones). And sinceCyanimmediately follows aconst,iotais reset to 0 which become’sCyan’s value;Magenta’s value is alsoiotabut at this pointiota’s value is 1. Similarly,Yellow’s value isiotawhose value is now 2. And if we addedBlackat the end (but within theconstgroup) it would be implicitly set toiotawhose value at that point would be 3.
On the other hand, if the right-hand code snippet didn’t haveiota,Cyanwould be set to 0 andMagenta would be set toCyan’s value andYellowwould be set to Magenta’s value—so they would all end up being set to 0. Similarly, ifCyanwas set to 9, then they would all be set to 9; or if Magentawas set to 5,Cyanwould be set to 0 (first in the group and not assigned an explicit value oriota),Magentawould be 5 (explicitly set), andYellowwould be 5 (the previous constant’s value).
It is also possible to useiotawith floating-point numbers, simple expressions, and custom types.
ptg7913109 type BitFlag int
const (
Active BitFlag = 1 << iota // 1 << 0 == 1 Send // Implicitly BitFlag = 1 << iota // 1 << 1 == 2 Receive // Implicitly BitFlag = 1 << iota // 1 << 2 == 4 )
flag := Active | Send
In this snippet we have created three bit flags of custom typeBitFlagand then set variableflag(of typeBitFlag) to the bitwiseORof two of them (soflaghas value3; Go’s bitwise flags are shown in Table 2.6,➤ 60). We could have omitted the custom type in which case Go would have made the constants untyped integers and inferredflag’s type asint. Variables of typeBitFlagcan have any intvalue; nonethelessBitFlagis a distinct type so can only be used in operations withints if converted to anint(or if theints are converted toBitFlags).
TheBitFlagtype is useful as it stands, but it isn’t very convenient for debugging.
If we were to printflag we would just get 3 with no indication of what that means. Go makes it really easy to control how values of custom types are print-ed, because thefmtpackage’s print functions will use a type’sString() method if it has one. So to make ourBitFlagtype print in a more informative way, we can simply add a suitableString()method to it. (Custom types and methods are covered fully in Chapter 6.)
func (flag BitFlag) String() string { var flags []string
if flag&Active == Active {
flags = append(flags, "Active") }
if flag&Send == Send {
flags = append(flags, "Send") }
if flag&Receive == Receive {
flags = append(flags, "Receive") }
if len(flags) > 0 { // int(flag) is vital to avoid infinite recursion!
return fmt.Sprintf("%d(%s)", int(flag), strings.Join(flags, "|")) }
return "0()"
}
This method builds up a (possibly empty) slice of strings for those bit fields that are set and then prints the bit field’s value as a decimalintand with the strings to indicate its value. (We could easily have printed the value as a binary number by replacing the%dformat specifier with%b.) As the comment notes, it is
essen-ptg7913109 tial that we convert theflag(of typeBitFlag) to its underlyinginttype when
passing it to thefmt.Sprintf()function, otherwise theBitFlag.String()method will be called recursively on theflagwhich will take us into an infinite recursion.
(The built-inappend()function is covered in §4.2.3,➤ 156; thefmt.Sprintf()and strings.Join()functions are covered in Chapter 3.)
Println(BitFlag(0), Active, Send, flag, Receive, flag|Receive)
0() 1(Active) 2(Send) 3(Active|Send) 4(Receive) 7(Active|Send|Receive)
This snippet shows howBitFlags with theString()method in place look when printed—clearly, this is much more useful for debugging than bare integers.
It is, of course, possible to create a custom type that represents a restricted range of integers, and to create a more elaborate custom enumeration type; we cover custom types more fully in Chapter 6. Go’s minimalist approach to enumerations is typical of the Go philosophy: Go aims to provide everything that programmers need—including many powerful and convenient features—while keeping the language as small, consistent, and fast (to build and run) as possible.