Go言語入門:言語仕様 -Vol.90-

スポンサーリンク
Go言語入門:言語仕様 -Vol.90- 用語解説
Go言語入門:言語仕様 -Vol.90-
この記事は約9分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語の言語仕様について解説しています。

スポンサーリンク

背景

Go言語を学び始めて、公式の「The Go Programming Language Specification(言語仕様書)」を開いてみたものの、「英語で書かれていて読むのが大変…」「専門用語ばかりで何を言っているのかわからない…」と感じたことはありませんか? 実は、多くのGo初心者が同じ壁にぶつかっています。

言語仕様書は、Go言語の「正式な取扱説明書」のような存在です。プログラミング言語がどのように動くのか、どんなルールで書くべきなのかが詳しく書かれていますが、その分、初めて読む人には難しく感じられるのも事実です。

そこでこの記事では、言語仕様書の導入部分を丁寧な日本語訳とともに、初心者の方でも理解しやすい補足説明を加えてお届けします。「強く型付けされている」「ガベージコレクション」「並行プログラミング」といった専門用語も、具体例を交えながらわかりやすく解説していきます。

言語仕様書は難しそうに見えますが、一つひとつの概念を丁寧に読み解いていけば、必ず理解できます。一緒に、Go言語の基礎をしっかり学んでいきましょう!

比較演算子(Comparison operators)

比較演算子は2つのオペランドを比較し、型なし真偽値を生成する。

==    等しい
!=    等しくない
<     より小さい
<=    以下
>     より大きい
>=    以上

いかなる比較においても、最初のオペランドは2番目のオペランドの型に代入可能であるか、またはその逆でなければならない。

等値演算子 ==!= は比較可能な型のオペランドに適用される。順序演算子 <<=>>= は順序付き型のオペランドに適用される。これらの用語と比較の結果は以下のように定義される:

  • 真偽値型は比較可能である。2つの真偽値は、両方とも true であるか両方とも false である場合に等しい。
  • 整数型は比較可能かつ順序付きである。2つの整数値は通常の方法で比較される。
  • 浮動小数点型は比較可能かつ順序付きである。2つの浮動小数点値は IEEE 754 標準の定義に従って比較される。
  • 複素数型は比較可能である。2つの複素数値 uv は、real(u) == real(v) かつ imag(u) == imag(v) の場合に等しい。
  • 文字列型は比較可能かつ順序付きである。2つの文字列値はバイト単位で辞書順に比較される。
  • ポインタ型は比較可能である。2つのポインタ値は、同じ変数を指しているか、両方とも値 nil を持つ場合に等しい。異なるゼロサイズ変数へのポインタは等しい場合も等しくない場合もある。
  • チャネル型は比較可能である。2つのチャネル値は、同じ make 呼び出しで作成されたか、両方とも値 nil を持つ場合に等しい。
  • 型パラメータでないインターフェース型は比較可能である。2つのインターフェース値は、同一の動的型を持ち動的値が等しい場合、または両方とも値 nil を持つ場合に等しい。
  • 非インターフェース型 X の値 x とインターフェース型 T の値 t は、型 X が比較可能であり XT を実装する場合に比較できる。t の動的型が X と同一であり、t の動的値が x と等しい場合に等しい。
  • 構造体型は、すべてのフィールド型が比較可能である場合に比較可能である。2つの構造体値は、対応するブランクでないフィールド値が等しい場合に等しい。フィールドはソース順に比較され、2つのフィールド値が異なった時点で(またはすべてのフィールドが比較された時点で)比較は停止する。
  • 配列型は、配列要素型が比較可能である場合に比較可能である。2つの配列値は、対応する要素値が等しい場合に等しい。要素はインデックスの昇順に比較され、2つの要素値が異なった時点で(またはすべての要素が比較された時点で)比較は停止する。
  • 型パラメータは、厳密に比較可能(以下を参照)である場合に比較可能である。

同一の動的型を持つ2つのインターフェース値の比較は、その型が比較可能でない場合、実行時パニックを引き起こす。この動作はインターフェース値の直接比較だけでなく、インターフェース値の配列やインターフェース値のフィールドを持つ構造体の比較にも適用される。

スライス、map、および関数型は比較可能ではない。ただし、特殊なケースとして、スライス、map、または関数の値を事前宣言された識別子 nil と比較することはできる。ポインタ、チャネル、およびインターフェース値の nil との比較も許可されており、上記の一般的なルールに従う。

const c = 3 < 4            // c は型なし真偽定数 true

type MyBool bool
var x, y int
var (
	// 比較の結果は型なし真偽値である。
	// 通常の代入ルールが適用される。
	b3        = x == y // b3 は bool 型を持つ
	b4 bool   = x == y // b4 は bool 型を持つ
	b5 MyBool = x == y // b5 は MyBool 型を持つ
)

型が厳密に比較可能であるのは、それが比較可能でありインターフェース型でもなく、インターフェース型で構成されてもいない場合である。具体的には:

  • 真偽値型、数値型、文字列型、ポインタ型、およびチャネル型は厳密に比較可能である。
  • 構造体型は、すべてのフィールド型が厳密に比較可能である場合に厳密に比較可能である。
  • 配列型は、配列要素型が厳密に比較可能である場合に厳密に比較可能である。
  • 型パラメータは、その型集合のすべての型が厳密に比較可能である場合に厳密に比較可能である。

解説

比較演算子の2つのグループ

比較演算子は大きく2グループに分かれます。

等値演算子(==!=:「等しいかどうか」を判定する。比較可能な型に使える。

順序演算子(<<=>>=:「大小関係」を判定する。順序付き型にしか使えない。

// 等値:多くの型で使える
3 == 3            // true
"abc" != "def"    // true

// 順序:数値と文字列だけ
3 < 5             // true
"abc" < "def"     // true(辞書順)
// true < false   // コンパイルエラー! bool に順序はない

型ごとの比較ルール早見表

どの型が何に対応するかを整理してみましょう。

== / !=< / >備考
bool
整数
浮動小数点NaN 関連の注意あり
複素数実部と虚部をそれぞれ比較
文字列バイト単位の辞書順
ポインタ同じ変数を指すか
チャネル同じ make で作られたか
インターフェースパニックの可能性あり
構造体全フィールドが比較可能なら
配列全要素が比較可能なら
スライスnil とだけ比較可能
mapnil とだけ比較可能
関数nil とだけ比較可能

比較できない型:スライス、map、関数

この3つは == で比較できません。ただし nil との比較だけは許されています。

var s []int
var m map[string]int
var f func()

// nil との比較だけ OK
fmt.Println(s == nil)  // true
fmt.Println(m == nil)  // true
fmt.Println(f == nil)  // true

// 値同士の比較はエラー
// fmt.Println(s == []int{})     // コンパイルエラー!
// fmt.Println(m == map[string]int{})  // コンパイルエラー!

スライスの中身を比較したい場合は slices.Equal(Go 1.21以降)や reflect.DeepEqual を使います。

import "slices"

a := []int{1, 2, 3}
b := []int{1, 2, 3}
fmt.Println(slices.Equal(a, b))  // true

インターフェースの比較とパニック

インターフェースの比較は危険を伴います。動的型が比較可能でない場合、実行時パニックが起きます。

var a interface{} = []int{1, 2}
var b interface{} = []int{1, 2}

fmt.Println(a == b)  // 実行時パニック! スライスは比較できない

コンパイラはインターフェースの中身の型を事前に知ることができないので、コンパイル時にはエラーになりません。このリスクは、インターフェース値のフィールドを持つ構造体や、インターフェース値を要素とする配列にも波及します。

type Item struct {
    Value interface{}
}

a := Item{Value: []int{1}}
b := Item{Value: []int{1}}
fmt.Println(a == b)  // 実行時パニック! 構造体の比較がフィールドの比較に進み、パニック

文字列の比較はバイト単位

文字列の大小比較はバイト単位の辞書順で行われます。

"abc" < "abd"    // true(3バイト目で c < d)
"abc" < "abcd"   // true(前方一致で短いほうが小さい)
"abc" < "ABC"    // false(小文字は大文字より大きい。'a'=97, 'A'=65)

日本語のような多バイト文字も同じルールです。文字としての順序ではなく、UTF-8 のバイト列として比較されます。

比較可能 vs 厳密に比較可能

この区別は、以前学んだ comparable 制約の話とつながります。

  • 比較可能(comparable)== が使える型。インターフェースも含む。
  • 厳密に比較可能(strictly comparable):比較可能だが、インターフェースを含まない型。
int                    // 厳密に比較可能 ✅
string                 // 厳密に比較可能 ✅
struct{ x int }        // 厳密に比較可能 ✅(フィールドが厳密に比較可能)
interface{}            // 比較可能だが、厳密には比較可能でない ❌
struct{ x interface{} } // 比較可能だが、厳密には比較可能でない ❌
[]int                  // 比較可能ですらない ❌

厳密に比較可能な型だけが comparable 制約を「実装」し、実行時パニックのリスクがありません。インターフェースが絡む型は「満足」はするけれど「実装」はしない、というのが以前学んだ話ですね。

比較結果は型なし真偽値

比較の結果は型なし真偽値なので、bool だけでなく bool を基底型とするカスタム型にも代入できます。

type MyBool bool

var x, y int
b1 := x == y          // b1 は bool 型
var b2 MyBool = x == y // b2 は MyBool 型(型なし真偽値なので代入可能)

これは型なし定数の仕組みと同じです。比較結果がまだ「型が決まっていない」状態なので、文脈に応じて適切な型に変換されます。

おわりに 

本日は、Go言語の言語仕様について解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました