iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
💻

Literal Constants

に公開

Qiita's "Reasons why 'c' can be assigned to integer types" was quite interesting.

https://qiita.com/Nabetani/items/0554d5b040d70525ec95

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 (\pi) is defined with a precision that far exceeds the basic float64 type:

math/const.go
// 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].

  1. Integer literals
  2. Floating-point literals
  3. Imaginary literals
  4. Rune literals
  5. String literals

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

https://zenn.dev/spiegel/articles/20210813-untyped-constant
https://zenn.dev/spiegel/articles/20211004-pointer-to-literal-value

脚注
  1. There are also boolean values like true/false and iota, but they are omitted in this article. For more on iota, please refer to my article "Writing properly about the iota constant generator" (Japanese). ↩︎

  2. Note that the value retrieved in a for-range loop is a copy, not a reference to the array. ↩︎

GitHubで編集を提案

Discussion