Constants are arguably present in every code file, and there are many benefits to using them.
- Avoid magic literals, i.e. numbers, strings, etc. that appear directly in the code. It is not possible to read the code and see what it means at a glance. It also avoids the inconsistencies that can occur when using literals. When their values need to be modified, constants only need to be modified in one place, while literals need to be modified in multiple places, making it easy to miss inconsistencies.
- Compared to variables, constants can perform compile-time optimizations.
The Go language also provides syntactic support for constants, which is largely consistent with the constants provided by other languages. But there are a few useful features of constants in Go that are worth knowing about.
Constants Base
The const keyword is used to define constants in the Golang:
Multiple constant definitions can be combined together, e.g. several of the above constant definitions can be written in the following form.
However, it is usually recommended to define constants of the same type, associated with each other, inside a group.
There is a big restriction on constants in Go: you can only define constants of basic types, i.e. boolean (bool), integer (unsigned uint/uint8/uint16/uint32/uint64/uintptr
, signed int/int8/int16/int32/int64
), floating-point (single-precision float32
, double-precision float64
), or the underlying type is one of these basic types. You cannot define constants of these types such as slices, arrays, pointers, structures, etc. For example, byte
has an underlying type of uint8
and rune
has an underlying type of int32
, see the Go source code builtin.go
:
Therefore, constants of class byte
or rune
can be defined:
Defining variables of other types will report an error during compilation.
iota
The Go language code often uses iota for constant definitions, here are a few Go source codes.
Standard library time source code:
|
|
Standard library net/http
source code:
iota
is a mechanism that facilitates the definition of our constants. In short, iota
acts independently in each constant definition group (each const
statement that appears separately counts as a group), and iota
appears in the constant expression used to initialize the value of the constant, the value of iota
is the line it is on in the constant group (starting at 0). The type and initialization expression can be omitted below a constant defined using iota, which then follows the type and initialization expression defined in the previous one. Let’s look at a few sets of examples:
This is also the most commonly used way, iota appears in the first line and its value is whatever it is. In the constant definition group above, One is on line 0 (note that it counts from 0) and iota is 0, so One = 0 + 1 = 1
. The next line Two omits the type and initialization expression, so Two follows the type int above, and the initialization expression is also iota + 1
. But this is line 1 of the definition group, and the value of iota is 1, so Two = 1 + 1 = 2
. The next line, Three, also omits the type and initialization expression, so Three follows Two and thus One’s type int, and the initialization expression is also iota + 1, but this is the second line of the definition, so Three = 2 + 1 = 3
. And so on.
We can use iota in very complex initialization expressions:
According to the above analysis Mask1~4
are 1, 3, 7, 15
in order.
In addition, there are odd numbers and even numbers:
In a group, iota does not necessarily appear in row 0, but the value is whatever the row in which it appears:
Above iota appears in row 2 (starting from 0) and the value of C is 2 + 1 = 3. D and E are 4, 5 respectively.
Be sure to note that the value of iota is equal to the number of rows it appears in the group, not the number of times it appears.
Values can be ignored by assigning to the empty identifier:
With all this talk about the use of iota, why should I use iota? In other words, what are the advantages of iota? I think there are two points.
- ease of definition, when the pattern is relatively fixed, we can just write out the first one, and the constants that follow don’t need to write out the type and initialization expressions.
- Convenient to adjust the order, add and delete constant definitions. If we want to adjust the order after defining a set of constants, using iota’s definition, we just need to adjust the position, no need to modify the initialization formula, because it is not written. The same goes for adding and deleting. If we write out the initialization formula one by one and delete the middle one, the subsequent values will have to be adjusted.
For example, the source code in net/http
:
If we need to add a constant that indicates the state that is being closed. Now just write the name of the added state:
If the initialization equation is written explicitly:
This adds the need to change subsequent values. Also a lot more characters need to be typed 😊:
No type constants
The Golang has a special type of constants, namely untyped constants. That is, we do not explicitly specify the type when defining it. Such constants can store values beyond the usual range of types:
|
|
Although untyped constants can store values outside the range of normal types and can do arithmetic operations on each other, they still need to be converted back to normal types when they are used (assigned to variables, passed as parameters). If the value exceeds the normal type range, the compilation will report an error. Each untyped constant has a default type, int for integers and float64 for floating point numbers (with decimal points or scientific notation), so in the above example, we define Integer2 as an untyped constant with the value of uint64 maximum + 1, which is allowed. But if we output the value of Integer2 directly, it will cause a compile error because Integer2 will be converted to int type by default, and it stores more values than the range of int. On the other hand, we can do operations with Integer2, such as dividing by 10, and the resulting value is in the int range and can be output. (I’m using a 64-bit machine)
The following floating point types are similar, Float3 is out of the range of float64 representation, so it can’t be output directly. But the result of Float3/Float2 is in the range of float64 and can be used.
Output of the above program:
The output also shows that the default types of integer and floating point are int and float64, respectively.
Combining the iota and untyped constants we can define a set of storage units:
|
|
ZB has actually reached 1180591620717411303424, which is beyond the range of int representation, but we can still define ZB and YB and still operate on them when we use them, as long as the final value to be used is within the range of normal types.
Summary
The text introduces the knowledge of constants, and it is sufficient to remember two main points.
- The value of iota is equal to the line in which it appears in the constant definition group (starting from 0).
- Untyped constants can define values that exceed the stored range, but they must be used in such a way that they can be transferred back to the normal type range, otherwise an error will be reported. Using untyped constants, we can perform arithmetic operations on large numbers at compile time
Reference https://darjun.github.io/2021/05/30/youdontknowgo/const/