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

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

このコミットは、Goコンパイラのガベージコレクタ(gc)におけるチャネルのrange操作に関するエラーメッセージを改善するものです。具体的には、受信専用ではない(つまり、送信専用の)チャネルに対してrangeを使用しようとした際に、より分かりやすいエラーメッセージが表示されるように修正されています。また、この変更に伴い、関連するテストケースが追加・修正されています。

コミット

commit ea9e93862d9b6fc0c5b53cdb204204923d653b8a
Author: Luuk van Dijk <lvd@golang.org>
Date:   Sun Nov 6 22:14:15 2011 +0100

    gc: Better error message for range over non-receive channel.
    
    Fixes #2354
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5346044
---
 src/cmd/gc/range.c | 4 ++++\n test/chan/perm.go  | 5 ++++-\n 2 files changed, 8 insertions(+), 1 deletion(-)\n

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

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

元コミット内容

このコミットの目的は、「受信専用ではないチャネルに対するrange操作のエラーメッセージを改善する」ことです。Go言語では、チャネルはデータの送受信に使用されますが、チャネルの型には「送受信可能(chan T)」、「送信専用(chan<- T)」、「受信専用(<-chan T)」の3種類があります。for...rangeループをチャネルに対して使用する場合、そのチャネルは受信可能である必要があります。このコミット以前は、送信専用チャネルに対してrangeを使用した場合のエラーメッセージが不明瞭であったため、開発者が問題を特定しにくかったと考えられます。

コミットメッセージにはFixes #2354と記載されており、これはGoのIssueトラッカーにおける2354番の課題を解決したことを示しています。また、R=rscはコードレビュー担当者(rsc, Rob Pike)を示し、CC=golang-devはGo開発者メーリングリストへの通知を示しています。https://golang.org/cl/5346044は、この変更がGoのコードレビューシステム(Gerrit)における変更リスト(Change List)のURLであることを示しています。

変更の背景

Go言語のfor...rangeステートメントは、スライス、配列、文字列、マップ、そしてチャネルといった様々なデータ構造をイテレートするために使用されます。チャネルに対するrangeは、チャネルがクローズされるまで、チャネルから値を受信し続けます。

しかし、Goのチャネルには方向性があり、送信専用チャネル(chan<- T)は値の送信のみが可能で、受信はできません。同様に、受信専用チャネル(<-chan T)は値の受信のみが可能で、送信はできません。送受信可能なチャネル(chan T)は、送受信の両方が可能です。

for...rangeがチャネルから値を受信する操作である以上、送信専用チャネルに対してrangeを使用することは論理的に誤りです。このコミット以前は、このような誤った使用に対するコンパイラのエラーメッセージが、開発者にとって直感的でなかった可能性があります。例えば、「チャネルが受信可能ではない」という直接的なメッセージではなく、より一般的な型エラーや、range操作の文脈から離れたメッセージが表示されていたかもしれません。

このコミットは、このような状況を改善し、開発者がコードの意図とコンパイラのエラーメッセージの間のギャップを埋める手助けをすることを目的としています。より明確なエラーメッセージを提供することで、デバッグの時間を短縮し、Goコードの品質向上に貢献します。

前提知識の解説

Go言語のチャネル

Go言語のチャネルは、ゴルーチン間で値を安全にやり取りするための通信メカニズムです。チャネルは型付けされており、特定の型の値のみを送信できます。

チャネルの宣言と使用には、以下の3つの主要な形式があります。

  1. 送受信可能なチャネル: chan T

    • 例: c := make(chan int)
    • このチャネルは、c <- value(送信)とvalue := <-c(受信)の両方に使用できます。
  2. 送信専用チャネル: chan<- T

    • 例: cs := make(chan<- int)
    • このチャネルは、cs <- value(送信)にのみ使用できます。value := <-csのような受信操作はコンパイルエラーになります。
  3. 受信専用チャネル: <-chan T

    • 例: cr := make(<-chan int)
    • このチャネルは、value := <-cr(受信)にのみ使用できます。cr <- valueのような送信操作はコンパイルエラーになります。

チャネルの方向性は、Goの型システムによって厳密にチェックされ、コンパイル時に不正な操作が検出されます。

for...rangeステートメントとチャネル

Goのfor...rangeステートメントは、チャネルから値を受信する際に使用できます。

ch := make(chan int)
go func() {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch) // チャネルをクローズ
}()

for v := range ch {
    fmt.Println(v) // チャネルがクローズされるまで値を受信し続ける
}

このループは、chから値が送信されるたびにそれを受信し、chがクローズされるとループを終了します。

Goコンパイラのgcパッケージ

src/cmd/gcは、Goコンパイラの主要な部分を構成するパッケージです。gcは「Go Compiler」の略であり、Goソースコードをコンパイルして実行可能なバイナリを生成する役割を担っています。このパッケージには、字句解析、構文解析、型チェック、中間コード生成、最適化、コード生成など、コンパイルプロセスの様々な段階が含まれています。

このコミットで変更されたsrc/cmd/gc/range.cファイルは、for...rangeステートメントの型チェックを担当する部分です。型チェックは、プログラムがGo言語の型規則に準拠していることを確認するコンパイル段階の重要なステップです。この段階で、例えば送信専用チャネルから値を受信しようとするような、型に関する不正な操作が検出されます。

技術的詳細

このコミットの技術的な変更は、主にGoコンパイラの型チェックロジックと、それに対応するテストケースの追加にあります。

src/cmd/gc/range.cの変更

src/cmd/gc/range.cファイルは、for...rangeステートメントの型チェックを行うC言語のソースファイルです。Goコンパイラの一部はC言語で書かれています。

変更箇所はtypecheckrange関数内にあります。この関数は、rangeステートメントの右辺の式(この場合はチャネル)の型をチェックします。

 	case TCHAN:
+		if(!(t->chan & Crecv)) {
+			yyerror("invalid operation: range %N (receive from send-only type %T)", n->right, n->right->type);
+			goto out;
+		}
 		t1 = t->type;
 		t2 = nil;
 		if(count(n->list) == 2)
  • case TCHAN:: rangeの右辺の型がチャネル(TCHAN)である場合の処理ブロックです。
  • if(!(t->chan & Crecv)): ここが追加された条件チェックです。
    • tはチャネルの型情報を表す構造体へのポインタです。
    • t->chanはチャネルの方向性を示すフラグのビットマスクです。
    • Crecvは「受信可能」であることを示すビットフラグです。
    • !(t->chan & Crecv)は、「チャネルが受信可能フラグを持っていない」ことを意味します。つまり、そのチャネルが受信専用ではない(送信専用または送受信可能だが、この文脈では送信専用が問題)場合に真となります。
  • yyerror("invalid operation: range %N (receive from send-only type %T)", n->right, n->right->type);:
    • この行は、上記の条件が真であった場合にコンパイルエラーを発生させます。
    • yyerrorはGoコンパイラがエラーメッセージを出力するために使用する関数です。
    • エラーメッセージは「invalid operation: range %N (receive from send-only type %T)」とフォーマットされます。
      • %Nrangeの右辺のノード(式)を表示します。
      • %Trangeの右辺の型の種類を表示します。
    • これにより、例えばfor _ = range cscsが送信専用チャネルの場合)のようなコードに対して、「invalid operation: range cs (receive from send-only type chan<- int)」といった、より具体的で分かりやすいエラーメッセージが出力されるようになります。
  • goto out;: エラーが発生したため、このtypecheckrange関数の残りの処理をスキップして、関数の終了ラベルoutにジャンプします。

この変更により、コンパイラは送信専用チャネルに対するrange操作をより早期かつ明確に検出し、適切なエラーメッセージを生成できるようになります。

test/chan/perm.goの変更

test/chan/perm.goは、Goのチャネルのパーミッション(方向性)に関するテストケースを含むファイルです。このコミットでは、送信専用チャネルに対するrange操作が正しくエラーとして検出されることを確認するためのテストが追加されています。

@@ -48,7 +48,10 @@ func main() {
 	case x := <-cs: // ERROR "receive"
 		_ = x
 	}\n-\t\n+\n+\tfor _ = range cs {// ERROR "receive"\n+\t}\n+\n \tclose(c)\
 \tclose(cs)\
 \tclose(cr)  // ERROR "receive"\
  • for _ = range cs {// ERROR "receive"}: この行が追加されました。
    • cschan<- intとして定義された送信専用チャネルです。
    • この行のコメント// ERROR "receive"は、Goのテストフレームワークにおける特別な指示です。これは、この行で「receive」という文字列を含むコンパイルエラーが発生することを期待していることを示します。
    • このテストケースの追加により、src/cmd/gc/range.cの変更が意図通りに機能し、送信専用チャネルに対するrange操作が正しくエラーとして扱われることが保証されます。

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

diff --git a/src/cmd/gc/range.c b/src/cmd/gc/range.c
index 1909c9ec77..25d1131ec3 100644
--- a/src/cmd/gc/range.c
+++ b/src/cmd/gc/range.c
@@ -46,6 +46,10 @@ typecheckrange(Node *n)\
 		break;\
 
 	case TCHAN:\
+		if(!(t->chan & Crecv)) {\
+			yyerror("invalid operation: range %N (receive from send-only type %T)", n->right, n->right->type);\
+			goto out;\
+		}\
 		t1 = t->type;\
 		t2 = nil;\
 		if(count(n->list) == 2)\
diff --git a/test/chan/perm.go b/test/chan/perm.go
index af054450ea..a43df19821 100644
--- a/test/chan/perm.go
+++ b/test/chan/perm.go
@@ -48,7 +48,10 @@ func main() {\
 	case x := <-cs: // ERROR "receive"\
 		_ = x\
 	}\
-\t\
+\n+\tfor _ = range cs {// ERROR "receive"\
+\t}\
+\n \tclose(c)\
 \tclose(cs)\
 \tclose(cr)  // ERROR "receive"\

コアとなるコードの解説

src/cmd/gc/range.cの変更点

この変更は、typecheckrange関数内でチャネル型を処理するcase TCHANブロックに追加されました。

  • if(!(t->chan & Crecv)):
    • trangeの対象となるチャネルの型情報を保持するポインタです。
    • t->chanはチャネルの方向性を示すビットフラグの集合です。
    • Crecvは「受信可能」であることを示す定数ビットフラグです。
    • &はビットAND演算子です。t->chan & Crecvは、チャネルの方向性フラグにCrecvが含まれているかどうかをチェックします。
    • !は論理NOT演算子です。したがって、!(t->chan & Crecv)は「チャネルが受信可能ではない」場合に真となります。これは、チャネルが送信専用(chan<- T)である場合や、方向性が指定されていないが受信操作が許可されていない場合(Goの型システムでは送信専用チャネルは受信操作を許可しない)に該当します。
  • yyerror("invalid operation: range %N (receive from send-only type %T)", n->right, n->right->type);:
    • 上記の条件が真、つまりrange操作が受信できないチャネルに対して行われた場合に、このエラーメッセージがコンパイラによって出力されます。
    • %Nrangeの右辺の式(例: cs)を、%Tはその式の型(例: chan<- int)をそれぞれ埋め込みます。
    • これにより、開発者は「送信専用の型から受信しようとしている」という具体的なエラー原因を即座に理解できます。
  • goto out;:
    • エラーが検出されたため、typecheckrange関数の残りの型チェック処理は不要となり、関数の終了点であるoutラベルに直接ジャンプして処理を終了します。これにより、無駄な処理を省き、エラー処理を効率化しています。

test/chan/perm.goの変更点

このテストファイルは、チャネルの送受信パーミッションに関する様々なシナリオを検証します。追加された行は、送信専用チャネルに対するrange操作が正しくコンパイルエラーとなることを確認するためのものです。

  • for _ = range cs {// ERROR "receive"}:
    • csfunc main()内でcs := make(chan<- int)として宣言されており、送信専用チャネルです。
    • この行は、送信専用チャネルcsに対してfor...rangeループを使用しようとしています。
    • // ERROR "receive"というコメントは、Goのテストツールがこの行で「receive」という文字列を含むエラーメッセージが出力されることを期待していることを示します。これは、src/cmd/gc/range.cで追加されたエラーメッセージ「invalid operation: range %N (receive from send-only type %T)」が正しく機能していることを検証します。

これらの変更により、Goコンパイラはより堅牢になり、開発者に対してチャネルの誤用に関するより明確なフィードバックを提供するようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント(チャネル、for...rangeステートメントに関する一般的な知識)
  • Goコンパイラのソースコード(src/cmd/gcの構造とyyerror関数の使用法に関する一般的な知識)
  • Goのテストフレームワークにおける// ERRORコメントの慣習に関する一般的な知識