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

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

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

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

スポンサーリンク

背景

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

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

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

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

アドレス演算子(Address operators)

T のオペランド x に対して、アドレス操作 &xx への型 *T のポインタを生成する。オペランドはアドレス指定可能でなければならない。すなわち、変数、ポインタ間接参照、またはスライスのインデックス操作のいずれかであるか、アドレス指定可能な構造体オペランドのフィールドセレクタであるか、アドレス指定可能な配列の配列インデックス操作でなければならない。アドレス指定可能性の要件の例外として、x は(括弧で囲まれていてもよい)複合リテラルであってもよい。x の評価が実行時パニックを引き起こす場合、&x の評価もまた実行時パニックを引き起こす。

ポインタ型 *T のオペランド x に対して、ポインタ間接参照 *xx が指す型 T の変数を表す。xnil の場合、*x の評価を試みると実行時パニックが発生する。

&x
&a[f(2)]
&Point{2, 3}
*p
*pf(x)

var x *int = nil
*x   // 実行時パニックを引き起こす
&*x  // 実行時パニックを引き起こす

解説

&(アドレス取得)と *(デリファレンス)

Go のポインタ操作は2つの演算子で行います。

x := 42
p := &x     // & でアドレスを取得 → p は *int 型
fmt.Println(*p)  // * でデリファレンス(ポインタの先の値を取得)→ 42

たとえるなら、& は「住所を調べる」、* は「住所を元に訪ねる」操作です。

// & : 変数 → ポインタ
// * : ポインタ → 変数の値

アドレス指定可能なもの・できないもの

& を使えるのは、メモリ上に確定した場所を持つものだけです。

アドレスを取れるもの(アドレス指定可能):

var x int
p1 := &x          // ✅ 変数

s := []int{1, 2, 3}
p2 := &s[0]        // ✅ スライスのインデックス

type Point struct{ X, Y int }
pt := Point{1, 2}
p3 := &pt.X        // ✅ アドレス指定可能な構造体のフィールド

arr := [3]int{1, 2, 3}
p4 := &arr[0]      // ✅ アドレス指定可能な配列のインデックス

アドレスを取れないもの:

p := &42           // ❌ リテラル(定数)
p := &(x + y)      // ❌ 式の結果
p := &getUser()    // ❌ 関数の戻り値

m := map[string]int{"a": 1}
p := &m["a"]       // ❌ mapの要素(mapは内部で再配置される可能性があるため)

mapの要素のアドレスが取れないのは、よくある落とし穴です。mapは内部的にデータを再配置することがあるため、要素の場所が変わる可能性があるからです。

複合リテラルへの & は特別に許可されている

通常、リテラルのアドレスは取れませんが、複合リテラルだけは例外です。

p := &Point{2, 3}  // ✅ 複合リテラルは特別にOK

これは非常によく使われるパターンです。なぜかというと、もしこれが許可されていなかったら、こう書く必要があるからです。

tmp := Point{2, 3}
p := &tmp            // いちいち一時変数を作るのは面倒

&Point{2, 3} と書けることで、ポインタを返す関数が簡潔に書けます。

func NewPoint(x, y int) *Point {
    return &Point{X: x, Y: y}  // 一行で済む
}

nilポインタのデリファレンスはパニック

nil ポインタの先を見ようとすると、実行時パニックが起きます。

var p *int = nil
fmt.Println(*p)  // 実行時パニック!

原文の最後の例 &*x もパニックします。一見「&* が打ち消し合って元に戻るのでは?」と思うかもしれませんが、Go では *x が先に評価されます。xnil なので *x の時点でパニックが起き、& の出番はありません。

var x *int = nil
&*x  // まず *x を評価 → nil デリファレンスでパニック!

ポインタの実用パターン

1. 関数で呼び出し元の値を変更する

func increment(p *int) {
    *p = *p + 1  // ポインタ経由で元の値を変更
}

x := 10
increment(&x)
fmt.Println(x)  // 11

2. 構造体のポインタを返す

func NewUser(name string) *User {
    return &User{Name: name}
}

3. nilチェックの習慣

func process(p *Point) {
    if p == nil {
        return  // nil ならすぐ返す
    }
    fmt.Println(p.X, p.Y)  // 安全にアクセス
}

ポインタを受け取る関数では、最初に nil チェックをする習慣をつけておくと、パニックを防げます。

おわりに 

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

よっしー
よっしー

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

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

コメント

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