LoginSignup
1
0

More than 3 years have passed since last update.

Goの定数がヤヤコシイ話

Last updated at Posted at 2020-12-19

問題

以下のコードを実行するとどうなるか?

package main

import "fmt"

func main() {
    const c = 2i * 3i
    var v int = c
    fmt.Printf("%T/%T", c, v)
}
  1. コンパイルエラー
  2. 実行時panic
  3. int/intが出力される
  4. complex128/intが出力される

14日目の記事で関連する問題が出題されています。先に見ておくといいかも:upside_down:

scthinkingtime.png

解答


解答

4. complex128/intが出力される

解説

Goにおける定数の扱いは独特で理解しづらいものです。本問題の要点は次の2つです。

  1. Goには型無し(untyped)の定数がある
  2. 「型無し」といっても、Goの型が定まってないだけで、実は「真偽値」「整数」「複素数」などの“種別”がある

定数の“種別”1は以下のものがあります。

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.
(Constants)

すなわち、“数値の定数”には「ルーン定数(rune constant)」「整数定数(integer constant)」「浮動小数点定数(floating-point constant)」「複素数定数(complex constant)」の4つの“種別”があります。

詳細

実際にmain()の実行を追ってみます。

1行目
    const c = 2i * 3i

2i3i虚数リテラル(imaginary literal)です。リテラルが定数として扱われるのは当然ですが、Goではこの場合特に「型無しの定数」になります。

Literal constants, true, false, iota, and certain constant expressions containing only untyped constant operands are untyped.
(Constants)

ただ「型無し」といっても先述の通り“種別”はあります。2i3iは(そもそも値が実数でないので2)「型無しの複素数定数」となります。

2i * 3iは「型無しの複素数定数」同士の乗算なので、結果も同じく「型無しの複素数定数」となります。値は−6なのですが整数定数にはならないことに注意しましょう。

Any other operation on untyped constants results in an untyped constant of the same kind; that is, a boolean, integer, floating-point, complex, or string constant.
(Constant expressions)

const c = …の定数宣言ではcに型を付けていないため、cも「型無しの複素数定数」(値は−6)となります。

If the type is omitted, the constants take the individual types of the corresponding expressions. If the expression values are untyped constants, the declared constants remain untyped and the constant identifiers denote the constant values.
(Constant declarations)

2行目
    var v int = c

int型変数vの初期値にcを指定したので、実質的に「cvへの代入」が発生します。従って、代入可能性(assignability)が問題になりますが、右辺が「型無し定数」である場合は表現可能性(representability)のみが要件になります。

A value x is assignable to a variable of type T ("x is assignable to T") if one of the following conditions applies:

  • …(略)…
  • x is an untyped constant representable by a value of type T.

(Assignability)

数値の表現可能性では「数値そのもの」だけが問題になります。今の場合、「−6」はintで表される整数値であるため、「複素数定数である」ということは無関係で、この代入は可能です。

ちなみに、ここでもし右辺が「型付き」(定数でない場合も含む)である場合は、左辺の型と一致していることが要求されます。つまり以下の例3はコンパイルエラーになります。

    var v int = float32(c) // エラー:cannot use float32(c) (type float32) as type int in assignment

最後の行になりました。

3行目
    fmt.Printf("%T/%T", c, v)

ここではcvという2つの式の型を考える必要があります。vは当然intとなりますが、「型無し」のcはどうすればいいでしょうか。このように4、「型無し」の定数に「他に制約がない状況」で型を与えないといけない場合は、「デフォルト型」(default 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.
(Constants)

つまり、引数のcは「complex128型の−6」として関数に渡されます。従って、complex128/intが出力されることになります。

まとめ

  1. Goには型無し(untyped)の定数がある
  2. 「型無し」といっても、Goの型が定まってないだけで、実は「真偽値」「整数」「複素数」などの“種別”がある

Goの定数はヤヤコシイ:frowning2:


  1. “種別”は本記事で仮に用いている用語であり、公式のGoの用語ではありません。 

  2. 0iも「型無しの複素数定数」と扱われるようなので、要するに「虚数リテラルは必ず複素数定数を表す」ということなのでしょう。規格では“An imaginary literal represents the imaginary part of a complex constant.”(Imaginary Literals)という記述しかなくてアレなんですが。 

  3. float32(c)は「float32型の浮動小数点定数の−6」となります。この式自体が合法なのは「−6がfloat32型で表現可能である」ためです。 

  4. 詳しくいうと:fmt.Printfの第2引数以降はinterface{}型の可変長引数であるため、実質的に「cinterface{}型変数に代入する」ことになります。変数が持つ値は常に型付きであり、かつ、interface{}型はあらゆる具象型により実装され得るので、「他に制約がない状況で型を与えないといけない」に該当します。 

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0