Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 10337] ファイルの概要

このコミットは、Go言語の公式チュートリアル(doc/go_tutorial.html および doc/go_tutorial.tmpl)を更新し、単方向チャネル(unidirectional channels)の概念とその使用方法について説明を追加するものです。具体的には、チャネルの方向性を示す構文(<-chan および chan<-)と、それがどのように型システムによってコードの安全性と可読性を向上させるかを解説しています。また、関連するサンプルコード(doc/progs/server.go および doc/progs/server1.go)も、単方向チャネルの利用に合わせて修正されています。

コミット

tutorial: describe unidirectional channels

R=golang-dev, adg, gri
CC=golang-dev
https://golang.org/cl/5370058

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a50ee009f7513a0c0ee4e9ab50980e1181c77e8e

元コミット内容

tutorial: describe unidirectional channels

R=golang-dev, adg, gri
CC=golang-dev
https://golang.org/cl/5370058

変更の背景

Go言語のチャネルは、ゴルーチン間の安全な通信を可能にする強力なプリミティブです。しかし、デフォルトのチャネルは双方向であり、送受信の両方に使用できます。これにより、チャネルを引数として受け取る関数が、意図せずチャネルに送信したり、チャネルから受信したりする可能性がありました。

このコミットの背景には、Go言語のチュートリアルにおいて、チャネルのより高度な利用法、特に「単方向チャネル」の概念を導入し、開発者がより堅牢で意図が明確なコードを書けるようにするという目的があります。単方向チャネルは、関数のシグネチャでチャネルの利用方向を明示することで、コンパイル時に誤った操作を防ぎ、コードの可読性と安全性を向上させます。この変更は、Go言語の設計思想である「明示性」と「安全性」をチュートリアルを通じてより深く理解してもらうためのものです。

前提知識の解説

Go言語のチャネルとゴルーチン

Go言語は、並行処理をサポートするために「ゴルーチン(goroutine)」と「チャネル(channel)」という2つの主要な概念を提供します。

  • ゴルーチン: Goランタイムによって管理される軽量なスレッドです。goキーワードを使って関数を呼び出すことで、新しいゴルーチンを起動できます。ゴルーチンは非常に軽量であるため、数千、数万のゴルーチンを同時に実行することが可能です。
  • チャネル: ゴルーチン間で値を安全に送受信するための通信メカニズムです。チャネルは、Goの「共有メモリによる通信ではなく、通信によるメモリ共有」という並行処理の哲学を体現しています。チャネルはmake(chan Type)で作成され、ch <- valueで送信、value := <-chで受信を行います。

チャネルの方向性(双方向 vs. 単方向)

Goのチャネルは、デフォルトでは双方向(bidirectional)です。つまり、同じチャネルを使ってデータの送信も受信も可能です。

ch := make(chan int) // 双方向チャネル
ch <- 1              // 送信
val := <-ch          // 受信

しかし、特定の関数がチャネルを引数として受け取る場合、その関数がチャネルに対して行う操作を制限したい場合があります。例えば、ある関数はチャネルからデータを受信するだけで、送信はしない、といったケースです。このような場合に「単方向チャネル(unidirectional channel)」が役立ちます。

  • 受信専用チャネル: <-chan Typeと記述します。このチャネルからはデータを受信することしかできません。
  • 送信専用チャネル: chan<- Typeと記述します。このチャネルにはデータを送信することしかできません。

単方向チャネルは、関数のシグネチャでチャネルの利用意図を明確にし、コンパイル時に誤った操作を防ぐための型安全性を提供します。

技術的詳細

このコミットの核となる技術的詳細は、Go言語の型システムがどのように単方向チャネルを扱うかという点にあります。

  1. 宣言と利用:

    • func server(op binOp, service <-chan *request): serviceチャネルは受信専用として宣言されています。server関数内では、req := <-serviceのように受信操作のみが許可されます。もしservice <- reqのような送信操作を試みると、コンパイルエラーになります。
    • func startServer(op binOp) chan<- *request: startServer関数は送信専用チャネルを返します。このチャネルは、startServerの呼び出し元がreq <- someValueのように送信操作のみを行うことを意図しています。
  2. 代入規則:

    • 双方向チャネルから単方向チャネルへの代入: これは許可されています。例えば、ch := make(chan int)という双方向チャネルがある場合、var recvOnly <-chan int = chvar sendOnly chan<- int = chのように、単方向チャネル型の変数に代入できます。これにより、元の双方向チャネルを、特定のコンテキスト(例えば関数内)で単方向として扱うことが可能になります。
    • 単方向チャネルから双方向チャネルへの代入: これは許可されていません。例えば、var recvOnly <-chan intがある場合、var ch chan int = recvOnlyはコンパイルエラーになります。これは、単方向チャネルが持つ制限を解除してしまうことになり、型安全性を損なうためです。
    • 単方向チャネルから別の単方向チャネルへの代入: 方向性が一致していれば可能です(例: <-chan intから<-chan intへ)。方向性が異なる場合は許可されません(例: <-chan intからchan<- intへ)。
  3. make関数と単方向チャネル:

    • make関数で直接単方向チャネルを作成することはできません。make(chan<- int)make(<-chan int)は無効です。チャネルは常にmake(chan Type)で双方向として作成され、その後、必要に応じて単方向チャネル型の変数に代入することで、そのチャネルの利用方向を制限します。これは、単方向チャネルの目的が、既存の双方向チャネルの「ビュー」を提供し、型安全性を高めることにあるためです。単方向チャネル自体が通信の主体となることはありません。

これらの規則により、Goの型システムは、チャネルの誤用を防ぎ、並行処理コードの意図をより明確にするのに役立ちます。

コアとなるコードの変更箇所

このコミットでは、主にGoチュートリアルのHTMLとテンプレートファイル、および関連するサンプルコードが変更されています。

  1. doc/go_tutorial.html および doc/go_tutorial.tmpl:

    • func server(op binOp, service chan *request)func server(op binOp, service <-chan *request) に変更されました。これにより、server関数がserviceチャネルからリクエストを受信する専用であることが明示されます。
    • func startServer(op binOp) chan *requestfunc startServer(op binOp) chan<- *request に変更されました。これにより、startServer関数が返すチャネルが、リクエストを送信する専用であることが明示されます。
    • func server(op binOp, service chan *request, quit chan bool)func server(op binOp, service <-chan *request, quit <-chan bool) に変更されました。quitチャネルも受信専用として宣言され、server関数が終了シグナルを受信する専用であることが示されます。
    • これらの変更に伴い、単方向チャネルに関する詳細な説明がチュートリアルに追加されました。チャネルの方向性を示す矢印の構文、送受信専用チャネルの役割、双方向チャネルから単方向チャネルへの代入規則などが解説されています。
  2. doc/progs/server.go および doc/progs/server1.go:

    • チュートリアルの説明に合わせて、server関数とstartServer関数のシグネチャが、単方向チャネルを使用するように修正されました。
      • server関数のservice引数が chan *request から <-chan *request へ。
      • startServer関数の戻り値が chan *request から chan<- *request へ。
      • server1.goserver関数のquit引数が chan bool から <-chan bool へ。

これらの変更は、チュートリアルの内容とサンプルコードを同期させ、単方向チャネルの概念を読者に正しく伝えることを目的としています。

コアとなるコードの解説

このコミットにおけるコアとなるコードの変更は、server関数とstartServer関数のチャネル引数および戻り値の型に単方向チャネルの指定が追加された点です。

server関数の変更

変更前:

func server(op binOp, service chan *request) {
    for {
        req := <-service
        go run(op, req) // don't wait for it
    }
}

変更後:

func server(op binOp, service <-chan *request) {
    for {
        req := <-service
        go run(op, req) // don't wait for it
    }
}

serviceチャネルの型が chan *request から <-chan *request (受信専用チャネル) に変更されました。これは、server関数がserviceチャネルからリクエストを「受信するだけ」であり、このチャネルにリクエストを「送信することはない」という意図を明確に示しています。これにより、コンパイラはserver関数内でserviceチャネルへの送信操作を検出した場合にエラーを報告し、誤用を防ぐことができます。

startServer関数の変更

変更前:

func startServer(op binOp) chan *request {
    req := make(chan *request)
    go server(op, req)
    return req
}

変更後:

func startServer(op binOp) chan<- *request {
    req := make(chan *request)
    go server(op, req)
    return req
}

startServer関数の戻り値の型が chan *request から chan<- *request (送信専用チャネル) に変更されました。startServer関数は、内部で双方向チャネルreqを作成し、その受信側をserverゴルーチンに渡し、送信側を自身の呼び出し元に返します。戻り値を送信専用チャネルとすることで、startServerの呼び出し元は、返されたチャネルに対してリクエストを「送信するだけ」であり、そこから何かを「受信することはない」という意図が明確になります。これにより、APIの利用方法がより明確になり、誤ったチャネル操作を防ぐことができます。

server1.goserver関数の変更

変更前:

func server(op binOp, service chan *request, quit chan bool) {
    // ...
}

変更後:

func server(op binOp, service <-chan *request, quit <-chan bool) {
    // ...
}

quitチャネルも<-chan bool(受信専用チャネル)として宣言されました。これは、server関数がquitチャネルから終了シグナルを「受信するだけ」であり、このチャネルにシグナルを「送信することはない」という意図を示します。

これらの変更は、Goのチャネルの型システムを活用して、関数のインターフェースにおけるチャネルの役割をより厳密に定義し、コードの安全性、可読性、および保守性を向上させる典型的な例です。

関連リンク

  • Go Change List: https://golang.org/cl/5370058

参考にした情報源リンク