In Go, a constant is a value with a literal representation such as a string of text, Boolean, or numbers. The value for a constant is static and cannot be changed after initial assignment. While the concept they represent is simple, constants, however, have some interesting properties that make them useful, especially when working with numeric values.
Constants are values that can be represented by a text literal in the language. One of the most interesting properties of constants is that their literal representations can either be treated as typed or untyped values. Unlike variables, which are intrinsically bound to a type, constants can be stored as untyped values in memory space. Without that type constraint, numeric constant values, for instance, can be stored with great precision.
The followings are examples of valid constant literal values that can be expressed in Go:
"Mastering Go" 'G' false 111009 2.71828 94314483457513374347558557572455574926671352 1e+500 5.0i
Go constant values can be bound to named identifiers using a constant declaration. Similar to a variable declaration, Go uses the const
keyword to indicate the declaration of a constant. Unlike variables, however, the declaration must include the literal value to be bound to the identifier, as shown in the following format:
const <identifier list> type = <value list or initializer expressions>
Constants cannot have any dependency that requires runtime resolution. The compiler must be able to resolve the value of a constant at compile time. This means all constants must be declared and initialized with a value literal (or an expression that results to a constant value).
The following snippet shows some typed constants being declared:
const a1, a2 string = "Mastering", "Go" const b rune = 'G' const c bool = false const d int32 = 111009 const e float32 = 2.71828 const f float64 = math.Pi * 2.0e+3 const g complex64 = 5.0i const h time.Duration = 4 * time.Second
golang.fyi/ch02/const.go
Notice in the previous source snippet that each declared constant identifier is explicitly given a type. As you would expect, this implies that the constant identifier can only be used in contexts that are compatible with its types. However, the next section explains how this works differently when the type is omitted in the constant declaration.
Constants are even more interesting when they are untyped. An untyped constant is declared as follows:
const <identifier list> = <value list or initializer expression>
As before, the keyword const
is used to declare a list of identifiers as constants along with their respective bounded values. However, in this format, the type specification is omitted in the declaration. As an untyped entity, a constant is merely a blob of bytes in memory without any type precision restrictions imposed. The following shows some sample declarations of untyped constants:
const i = "G is" + " for Go " const j = 'V' const k1, k2 = true, !k1 const l = 111*100000 + 9 const m1 = math.Pi / 3.141592 const m2 = 1.414213562373095048801688724209698078569671875376... const m3 = m2 * m2 const m4 = m3 * 1.0e+400 const n = -5.0i * 3 const o = time.Millisecond * 5
golang.fyi/ch02/const.go
From the previous code snippet, the untyped constant m2
is assigned a long decimal value (truncated to fit on the printed page as it goes another 17 digits). Constant m4
is assigned a much larger number of m3 x 1.0e+400
. The entire value of the resulting constant is stored in memory without any loss in precision. This can be an extremely useful tool for developers interested in computations where a high level of precision is important.
Untyped constant values are of limited use until they are assigned to variables, used as function parameters, or are part of an expression assigned to a variable. In a strongly-typed language like Go, this means there is a potential for some type adjustment to ensure that the value stored in the constant can be properly assigned to the target variable. One advantage of using untyped constants is that the type system relaxes the strict application of type checking. An untyped constant can be assigned to different, though compatible, types of different precision without any complaint from the compiler, as shown in the following example:
const m2 = 1.414213562373095048801688724209698078569671875376... var u1 float32 = m2 var u2 float64 = m2 u3 := m2
The previous snippet shows the untyped constant m2
being assigned to two variables of different floating-point precisions, u1
and u2
, and to an untyped variable, u3
. This is possible because constant m2
is stored as a raw untyped value and can therefore be assigned to any variable compatible with its representation (a floating point).
While the type system will accommodate the assignment of m2
to variables of different precision, the resulting assignment is adjusted to fit the variable type, as noted in the following:
u1 = 1.4142135 //float32 u2 = 1.4142135623730951 //float64
What about variable u3
, which is itself an untyped variable? Since u3
does not have a specified type, it will rely on type inference from the constant value to receive a type assignment. Recall from the discussion in the section Omitting Variable Types earlier, that constant literals are mapped to basic Go types based on their textual representations. Since constant m2
represents a decimal value, the compiler will infer its default to be a float64
, which will be automatically assigned to variable u3
, as shown:
U3 = 1.4142135623730951 //float64
As you can see, Go's treatment of untyped raw constant literals increases the language's usability by automatically applying some simple, but effective, type inference rules without sacrificing type-safety. Unlike other languages, developers do not have to explicitly specify the type in the value literal or perform some sort of typecast to make this work.
As you may have guessed, constant declarations, can be organized as code blocks to increase readability. The previous example can be rewritten as follows:
const ( a1, a2 string = "Mastering", "Go" b rune = 'G' c bool = false d int32 = 111009 e float32 = 2.71828 f float64 = math.Pi * 2.0e+3 g complex64 = 5.0i h time.Duration = 4 * time.Second ... )
golang.fyi/ch02/const2.go
One interesting usage of constants is to create enumerated values. Using the declaration block format (shown in the preceding section), you can easily create numerically increasing enumerated integer values. Simply assign the pre-declared constant value iota
to a constant identifier in the declaration block, as shown in the following code sample:
const ( StarHyperGiant = iota StarSuperGiant StarBrightGiant StarGiant StarSubGiant StarDwarf StarSubDwarf StarWhiteDwarf StarRedDwarf StarBrownDwarf )
golang.fyi/ch02/enum0.go
The compiler will then automatically do the following:
iota
with a value of zeroiota
, or zero, to the first constant member (StarHyperGiant
)int
value increased by oneSo the previous list of constants would be assigned a sequence of values going from zero to nine. Whenever const
appears as a declaration block, it resets the counter to zero. In the following snippet, each set of constants is enumerated from zero to four separately:
const ( StarHyperGiant = iota StarSuperGiant StarBrightGiant StarGiant StarSubGiant ) const ( StarDwarf = iota StarSubDwarf StarWhiteDwarf StarRedDwarf StarBrownDwarf )
golang.fyi/ch02/enum1.go
By default, an enumerated constant is declared as an untyped integer value. However, you can override the default type of the enumerated values by providing an explicit numeric type for your enumerated constants, as shown in the following code sample:
const ( StarDwarf byte = iota StarSubDwarf StarWhiteDwarf StarRedDwarf StarBrownDwarf )
You can specify any numeric type that can represent integers or floating point values. For instance, in the preceding code sample, each constant will be declared as type byte
.
When iota
appears in an expression, the same mechanism works as expected. The compiler will apply the expression for each successive increasing value of iota
. The following example assigns even numbers to the enumerated members of the constant declaration block:
const ( StarHyperGiant = 2.0*iota StarSuperGiant StarBrightGiant StarGiant StarSubGiant )
golang.fyi/ch02/enum2.go
As you may expect, the previous example assigns an even value to each enumerated constants, starting with 0, as shown in the following output:
StarHyperGiant = 0 [float64]
StarSuperGiant = 2 [float64]
StarBrightGiant = 4 [float64]
StarGiant = 6 [float64]
StarSubGiant = 8 [float64]
When working with enumerated constants, you may want to throw away certain values that should not be part of the enumeration. This can be accomplished by assigning iota to the blank identifier at the desired position in the enumeration. For instance, the following skips the values 0 and 64
:
_ = iota // value 0 StarHyperGiant = 1 << iota StarSuperGiant StarBrightGiant StarGiant StarSubGiant _ // value 64 StarDwarf StarSubDwarf StarWhiteDwarf StarRedDwarf StarBrownDwarf
golang.fyi/ch02/enum3.go
Since we skip iota
position 0
, the first assigned constant value is at position 1
. This results in expression 1 << iota
resolving to 1 << 1 = 2
. The same is done at the sixth position, where expression 1 << iota
returns 64
. That value will be skipped and not assigned to any constant, as shown in the following output:
StarHyperGiant = 2
StarSuperGiant = 4
StarBrightGiant = 8
StarGiant = 16
StarSubGiant = 32
StarDwarf = 128
StarSubDwarf = 256
StarWhiteDwarf = 512
StarRedDwarf = 1024
StarBrownDwarf = 2048
3.145.86.211