iTranslated by AI
Literal Constants
Qiita's "Reasons why 'c' can be assigned to integer types" was quite interesting.
Especially the last part about zig made me go "Huh, I didn't know that."
The article above likely prioritizes comparing different languages and intentionally omits detailed explanations. Since I'm here, I'll write a bit about Go's "literal constants".
Go Constants
The Go language specification explains "constants" as follows:
There are boolean constants, rune constants, integer constants, floating-point constants, complex constants, and string constants. Rune, integer, floating-point, and complex constants are collectively called numeric constants.
(via “The Go Programming Language Specification”)
Furthermore, regarding numeric constants:
Numeric constants represent exact values of arbitrary precision and do not overflow. Consequently, there are no constants denoting the IEEE-754 negative zero, infinity, and not-a-number values.
Constants may be typed or untyped. Literal constants, true, false, iota, and certain constant expressions containing only untyped constant operands are untyped.
(via “The Go Programming Language Specification”)
For example, in the standard math package, the constant for pi (float64 type:
// Mathematical constants.
const (
Pi = 3.14159265358979323846264338327950288419716939937510582097494459 // https://oeis.org/A000796
)
Untyped constants have their type determined when they are used in a variable declaration or assignment (they are actually evaluated at compile time).
An untyped constant has a default type which is the type to which the constant is implicitly converted in contexts where a typed value is required, for instance, in a short variable declaration such as i := 0 where there is no explicit type. The default type of an untyped constant is bool, rune, int, float64, complex128 or string respectively, depending on whether it is a boolean, rune, integer, floating-point, complex, or string constant.
(via “The Go Programming Language Specification”)
As I will explain later, a character enclosed in single quotes like 'c' is called a rune literal and represents a rune constant. Therefore, in a short variable declaration:
r := 'c'
The variable r is declared as type rune and given the initial value 'c' (U+0063). Furthermore, since 'c' is also an untyped numeric constant, a variable explicitly declared as:
var r int = 'c'
will have the type int and an initial value of 0x63. On the other hand, `
"c" enclosed in double quotes is a string literal, even for the same character c, so
var r int = "c" // cannot use "c" (untyped string constant) as int value in variable declaration
results in a compilation error. By the way, since string and rune arrays are mutually convertible, it won't be a compilation error (lol) if you explicitly do this:
package main
import "fmt"
func main() {
var r rune = []rune("c")[0]
fmt.Printf("%#U", r) // U+0063 'c'
}
Literal Representations of Constants
There are five literal representations for constants[1].
Let's look at each of them below.
Integer Literals
Integer literals are literal representations of integer constants and can be written in the following formats:
42
4_2
0600
0_600
0b0110
0B0110_0011
0o600
0O600 // second character is capital letter 'O'
0xBadFace
0xBad_Face
0x_67_7a_2f_cc_40_c6
Note that _ does not have any significance regarding the value but can be inserted anywhere as a digit separator.
Since integer literals also behave as untyped numeric constants, you can also write code like this:
package main
import "fmt"
func main() {
n := 0b0110_0011
fmt.Printf("%d\n", n) // 99
var f float64 = 0b0110_0011
fmt.Printf("%f\n", f) // 99.000000
var r rune = 0b0110_0011
fmt.Printf("%#U\n", r) // U+0063 'c'
}
Floating-point Literals
Floating-point literals are literal representations of floating-point constants and can be written in the following formats:
0.
72.40
072.40 // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5
1_5. // == 15.0
0.15e+0_2 // == 15.0
0x1p-2 // == 0.25
0x2.p10 // == 2048.0
0x1.Fp+0 // == 1.9375
0X.8p-0 // == 0.5
0X_1FFFP-16 // == 0.1249847412109375
While it's impressive that these can be written in the internal representation of floating-point numbers (IEEE 754), they are rarely used (lol).
If the fractional part is 0, they can also be used for initializing variable declarations of integer types (including byte and rune).
package main
import "fmt"
func main() {
var n int = 99.0
fmt.Printf("%d\n", n) // 99
var r rune = 99.0
fmt.Printf("%#U\n", r) // U+0063 'c'
}
However, if the fractional part is not 0, a compilation error occurs.
var n int = 4.56 // cannot use 4.56 (untyped float constant) as int value in variable declaration (truncated)
In this case, the literal value can be converted to an integer type by first assigning it to a variable (the fractional part will be truncated).
package main
import "fmt"
func main() {
f := 4.56
var n int = int(f)
fmt.Printf("%d\n", n) // 4
}
Imaginary Literals
Imaginary literals are literal representations of the imaginary part of complex constants and can be written in the following formats:
0i
0123i // == 123i for backward-compatibility
0o123i // == 0o123 * 1i == 83i
0xabci // == 0xabc * 1i == 2748i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i
0x1p-2i // == 0x1p-2 * 1i == 0.25i
Complex constants can be represented using imaginary literals as follows:
package main
import "fmt"
func main() {
c := 1.23 + 4.56i
fmt.Printf("%f\n", c) // (1.230000+4.560000i)
}
Naturally, a complex constant with a non-zero imaginary part cannot be used to initialize variable declarations for integer or floating-point types.
var f float64 = 1.23 + 4.56i // cannot use 1.23 + 4.56i (untyped complex constant (1.23 + 4.56i)) as float64 value in variable declaration (overflows)
Of course, conversion is also impossible after assigning it to a variable (since you cannot convert a complex type to a floating-point type in the first place).
c := 1.23 + 4.56i
var f = float64(c) // cannot convert c (variable of type complex128) to type float64
To extract the real and imaginary parts from a complex type, use the built-in real() or imag() functions.
package main
import "fmt"
func main() {
c := 1.23 + 4.56i
fmt.Printf("real part: %f\n", real(c)) // real part: 1.230000
fmt.Printf("imaginary part: %f\n", imag(c)) // imaginary part: 4.560000
}
For operations on complex types, it's best to use the standard math/cmplx package.
Rune Literals
The rune type is an integer type that represents a Unicode code point as its internal representation. Rune literals are literal representations of rune constants and can be written in the following formats:
'a'
'ä'
'本'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'
The point is that it must be a single code point (pun intended); for example, trying to represent an emoji made of multiple code points as a rune literal results in a compilation error.
const emoji = '👍🏼' // more than one character in rune literal
It seems that as long as it can be evaluated as an integer, the compiler doesn't care much about the value itself:
package main
import "fmt"
func main() {
var r rune = -1.0
fmt.Printf("%#U\n", r) // U+FFFFFFFFFFFFFFFF
}
Writing something as frightening as this is apparently possible (Wait, wasn't rune an alias for int32? Oh well).
String Literals
String literals are literal representations of string constants and can be written in the following formats:
`abc` // same as "abc"
`\n
\n` // same as "\\n\n\\n"
"\n"
"\"" // same as `"`
"Hello, world!\n"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"
String literals are assumed to be in UTF-8 encoding. Furthermore, string constants represented by string literals can only be used for the string type and behave as immutable values. For example, they cannot be used as initial values for byte or rune array declarations as shown below:
var b []byte = "日本語" // cannot use "日本語" (untyped string constant) as []byte value in variable declaration
var r []rune = "日本語" // cannot use "日本語" (untyped string constant) as []rune value in variable declaration
In this case, they can be used without any problems by explicitly converting them:
package main
import "fmt"
func main() {
var b []byte = []byte("日本語")
fmt.Println(b) // [230 151 165 230 156 172 232 170 158]
var r []rune = []rune("日本語")
fmt.Println(r) // [26085 26412 35486]
}
Alternatively, you can extract them in rune units using a for-range loop[2].
package main
import "fmt"
func main() {
for _, r := range "日本語" {
fmt.Printf("%#U\n", r)
}
// Output:
// U+65E5 '日'
// U+672C '本'
// U+8A9E '語'
}
References
-
There are also boolean values like
true/falseandiota, but they are omitted in this article. For more oniota, please refer to my article "Writing properly about the iota constant generator" (Japanese). ↩︎ -
Note that the value retrieved in a
for-rangeloop is a copy, not a reference to the array. ↩︎
Discussion