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

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

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

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

スポンサーリンク

背景

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

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

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

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

go文(Go statements)

go 文は、同じアドレス空間内で、独立した並行制御スレッド、すなわちゴルーチンとして関数呼び出しの実行を開始する。

GoStmt = "go" Expression .

式は関数またはメソッド呼び出しでなければならない。括弧で囲むことはできない。組み込み関数の呼び出しは式文と同様に制限される。

関数値とパラメータは呼び出し元のゴルーチンで通常どおり評価されるが、通常の呼び出しとは異なり、プログラムの実行は呼び出された関数の完了を待たない。代わりに、関数は新しいゴルーチンで独立して実行を開始する。関数が終了すると、そのゴルーチンも終了する。関数に戻り値がある場合、関数の完了時にそれらは破棄される。

go Server()
go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)

解説

go文ってなに?

go 文は、関数を別のゴルーチンで並行に実行するための構文です。Go の並行処理の中核をなす機能ですね。

go doSomething()  // doSomething を別のゴルーチンで実行開始
fmt.Println("すぐここに来る")  // 完了を待たずに次の行へ進む

go をつけるだけで、その関数呼び出しが「バックグラウンド」で動き始めます。呼び出し元は待たずに先に進みます。

ゴルーチンとは

ゴルーチンは、Go ランタイムが管理する軽量なスレッドです。OS のスレッドに比べてはるかに少ないメモリ(数KB)で起動でき、数千〜数百万のゴルーチンを同時に走らせることも可能です。

// 1000個のゴルーチンを同時に起動
for i := 0; i < 1000; i++ {
    go func(n int) {
        fmt.Println(n)
    }(i)
}

引数は呼び出し元で評価される

これは重要なポイントです。go f(x, y) と書いたとき、fxy呼び出し元のゴルーチンで即座に評価されます。その後、評価済みの値を使って新しいゴルーチンで f が実行されます。

x := 10
go fmt.Println(x)  // x=10 がここで評価される
x = 20              // この変更は上の Println に影響しない

もし引数の評価が新しいゴルーチン側で行われたら、x10 なのか 20 なのかわからなくなってしまいますよね。

無名関数との組み合わせ

go は無名関数(関数リテラル)と組み合わせて使うことが多いです。

go func() {
    // ここが別のゴルーチンで実行される
    heavyProcess()
}()  // ← 末尾の () で即座に呼び出し

引数を渡す場合:

go func(ch chan<- bool) {
    for {
        time.Sleep(10 * time.Second)
        ch <- true
    }
}(c)  // c を引数として渡す

戻り値は破棄される

ゴルーチンとして起動した関数の戻り値は受け取れません。

go fmt.Println("hello")  // Println は (int, error) を返すが、捨てられる

// 戻り値が必要なら、チャネル経由で受け取る
result := make(chan int)
go func() {
    result <- expensiveCalc()
}()
v := <-result  // 結果を受信

よくある落とし穴:main の終了

main 関数が終了すると、すべてのゴルーチンも強制終了されます。

func main() {
    go fmt.Println("hello")  // ゴルーチンを起動
    // main がすぐ終了 → "hello" が出力される前にプログラム終了!
}

ゴルーチンの完了を待つには、チャネルや sync.WaitGroup を使います。

func main() {
    done := make(chan struct{})
    go func() {
        fmt.Println("hello")
        close(done)
    }()
    <-done  // ゴルーチンの完了を待つ
}

sync.WaitGroup を使うパターンもよく使われます。

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            fmt.Println(n)
        }(i)
    }
    wg.Wait()  // すべてのゴルーチンが Done() を呼ぶまで待つ
}

組み込み関数の制限

式文と同じルールが適用されます。lencapappend などの副作用のない組み込み関数は go で使えません。

go len("foo")    // コンパイルエラー!
go fmt.Println() // OK!

go 文は括弧で囲めない

go (doSomething())   // コンパイルエラー!
go doSomething()     // OK!

これは go が文であって演算子ではないことを反映しています。go の対象は関数呼び出しそのものでなければなりません。

おわりに 

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

よっしー
よっしー

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

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

コメント

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