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

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

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

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

スポンサーリンク

背景

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

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

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

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

型制約の満足(Satisfying a type constraint)

型引数 T が型制約 C を満足するのは、TC によって定義される型集合の要素である場合、すなわち TC を実装する場合である。例外として、厳密に比較可能な型制約は、比較可能な(必ずしも厳密に比較可能でなくてもよい)型引数によっても満足されうる [Go 1.20]。より正確には:

T が制約 C を満足するのは、以下のいずれかの場合である:

  • TC を実装する。または、
  • Cinterface{ comparable; E } という形式で書ける場合(ここで E は基本インターフェース)に、T が比較可能であり、かつ TE を実装する。
type argument      type constraint                // 制約の満足

int                interface{ ~int }              // 満足する: int は interface{ ~int } を実装する
string             comparable                     // 満足する: string は comparable を実装する(string は厳密に比較可能)
[]byte             comparable                     // 満足しない: スライスは比較可能ではない
any                interface{ comparable; int }   // 満足しない: any は interface{ int } を実装しない
any                comparable                     // 満足する: any は比較可能であり、基本インターフェース any を実装する
struct{f any}      comparable                     // 満足する: struct{f any} は比較可能であり、基本インターフェース any を実装する
any                interface{ comparable; m() }   // 満足しない: any は基本インターフェース interface{ m() } を実装しない
interface{ m() }   interface{ comparable; m() }   // 満足する: interface{ m() } は比較可能であり、基本インターフェース interface{ m() } を実装する

制約満足ルールに例外があるため、型パラメータ型のオペランドの比較は実行時にパニックを起こす可能性がある(たとえ comparable な型パラメータが常に厳密に比較可能であるとしても)。


解説

この節のテーマ

前回は「型制約とは何か」を学びましたね。今回は、「ある型が、ある型制約を満足するかどうか」をコンパイラがどう判定するのか、そのルールについてのお話です。

ジェネリクスの関数を呼び出すとき、コンパイラは裏側で「渡された型引数が制約に合っているか?」をチェックしています。そのチェックの仕組みを見ていきましょう。

基本ルール:実装していれば満足する

一番シンプルなケースからいきますね。

func Double[T interface{ ~int }](v T) T {
    return v * 2
}

ここに int を渡せるでしょうか? intinterface{ ~int } が定義する型集合に含まれるので、「実装している」=「満足する」。問題なくOKです。

Double(42)  // int は interface{ ~int } を満足する → OK!

ここまでは素直ですね。

例外ルール:comparable だけ特別扱い(Go 1.20)

ややこしいのはここからです。前回、any(= interface{})は comparable を「実装しない」と学びましたよね。厳密に比較可能ではないから、というのが理由でした。

でも Go 1.20 で例外ルールが追加されました。comparable を含む制約については、厳密に比較可能でなくても、「比較可能」でありさえすれば満足する、というルールです。

なぜこんな例外を作ったのでしょう? 実用上の理由です。anymap のキーとして使えるのに、ジェネリクスの comparable 制約には渡せない……これでは不便すぎますよね。そこで「実用性を優先して緩和しよう」となったわけです。

func Keys[K comparable, V any](m map[K]V) []K {
    var keys []K
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

// any は comparable を「実装」しないが「満足」する
// → Go 1.20 以降、これはコンパイルが通る!
m := map[any]string{1: "one", "two": "2"}
Keys(m)  // OK!

例外ルールの正確な条件

例外が適用されるには、制約 Cinterface{ comparable; E } という形で書ける必要があります。ここで E基本インターフェース(メソッドだけで構成され、型要素を含まないインターフェース)です。

この条件のもとで、型 T が以下の両方を満たせば、制約を満足します。

  1. T が比較可能である(== が使える)
  2. TE の部分を実装する

具体例で確認してみましょう。

anycomparable :満足する ✅

comparableinterface{ comparable; any } と同じと考えられます(any は何のメソッドも要求しない基本インターフェース)。any は比較可能で、any を実装するので、両方の条件を満たします。

anyinterface{ comparable; int } :満足しない ❌

こちらは E の部分が int です。int は型要素を含むので基本インターフェースではなく、そもそも anyinterface{ int } を実装しません。例外ルールの出番はありません。

anyinterface{ comparable; m() } :満足しない ❌

Einterface{ m() } という基本インターフェースです。any は比較可能ですが、m() メソッドを持っていないので E を実装できず、条件2を満たしません。

表の読み方ガイド

仕様に載っている表を、もう少しかみ砕いて整理してみますね。

型引数制約結果理由
intinterface{~int}基本ルール。int~int に含まれる
stringcomparable基本ルール。string は厳密に比較可能
[]bytecomparableスライスはそもそも比較できない
anycomparable例外ルール。比較可能+基本IF any を実装
struct{f any}comparable例外ルール。構造体は比較可能+基本IF any を実装
interface{m()}interface{comparable; m()}例外ルール。比較可能+m() を持つ

パニックの危険性に注意!

例外ルールのおかげでコンパイルは通るようになりますが、実行時の安全は保証されません。原文の最後の一文はこの警告です。

func IsEqual[T comparable](a, b T) bool {
    return a == b  // コンパイルは通る
}

var x any = []int{1, 2}
var y any = []int{1, 2}
IsEqual(x, y)  // 実行時パニック! 中身がスライスなので比較できない

any 型の変数には何でも入れられます。中身がスライスや map だった場合、== を使った瞬間にパニック(実行時エラー)が起きます。コンパイラは「any は比較可能」として通しますが、中身の型までは見てくれないんですね。

これは便利さと安全性のトレードオフです。Go 1.20 の例外ルールは実用性を優先した設計なので、使う側がこのリスクを理解しておくことが大切です。

おわりに 

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

よっしー
よっしー

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

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

コメント

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