[インデックス 1330] ファイルの概要
このコミットは、Go言語の初期段階における型宣言の制約に関する重要な変更を導入しています。具体的には、map
、chan
(チャネル)、string
型の宣言がポインタ形式でなければならないという制限が課されました。これは、これらの型が内部的にどのように扱われるか、特にメモリ管理とセマンティクスに関する設計思想を反映しています。
コミット
commit c7ab3327442ab0a597f0efa7b6a5b465aec28744
Author: Ken Thompson <ken@golang.org>
Date: Thu Dec 11 16:09:45 2008 -0800
restrict declarations of type map/chan/string
(they must be pointers)
R=r
OCL=21009
CL=21009
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c7ab3327442ab0a597f0efa7b6a5b465aec28744
元コミット内容
map
、chan
、string
型の宣言を制限する(これらはポインタ形式でなければならない)。
変更の背景
Go言語の設計初期において、map
、chan
、string
といった複合型や参照型がどのようにメモリ上で表現され、プログラム内で扱われるべきかという議論がありました。これらの型は、その性質上、値のコピーではなく参照渡しがセマンティクス的に適切であると判断されました。
例えば、map
は可変長のデータ構造であり、その内容が変更される可能性があります。chan
はゴルーチン間の通信チャネルであり、複数のゴルーチンが同じチャネルを参照し、送受信を行うことが前提となります。string
は不変ですが、その内部表現はバイト列へのポインタと長さで構成されており、効率的なコピーのためにはポインタとしての扱いが望ましい場合があります。
このコミットが行われた2008年12月は、Go言語がまだ一般に公開される前の非常に初期の段階です(Go言語の公式発表は2009年11月)。この時期には、言語の基本的な型システム、メモリモデル、およびセマンティクスに関する重要な決定が日々行われていました。この変更は、これらの特定の型が「値」としてではなく「参照」として扱われるべきであるという設計上の決定をコンパイラレベルで強制するためのものです。これにより、開発者がこれらの型を誤って値型のように扱ってしまうことによる、予期せぬ動作や非効率なコードの生成を防ぐ狙いがありました。
特に、map
やchan
のようなデータ構造は、その実体がヒープに割り当てられ、変数自体はそのヒープ上の実体へのポインタを持つことが一般的です。もしこれらの型がポインタとして強制されなければ、開発者が誤ってこれらの型の「値」を宣言しようとした場合、コンパイラはそれをどのように解釈すべきか、あるいはどのようなコードを生成すべきかという問題に直面します。このコミットは、その曖昧さを排除し、これらの型が常にポインタとして扱われるという明確なルールを確立することで、言語のセマンティクスを単純化し、コンパイラの設計を容易にしました。
前提知識の解説
このコミットを理解するためには、以下のGo言語の基本的な概念とコンパイラの動作に関する知識が必要です。
- Go言語の型システム: Go言語には、値型(int, bool, structなど)と参照型(slice, map, channel, pointer, functionなど)があります。
- 値型: 変数に直接値が格納されます。関数に渡される際には値がコピーされます。
- 参照型: 変数には、実際のデータが格納されているメモリ上の場所(ヒープ)へのアドレス(ポインタ)が格納されます。関数に渡される際には、このアドレスがコピーされます。これにより、複数の変数が同じ基盤となるデータを参照し、共有・変更することが可能になります。
map
(マップ): キーと値のペアを格納するハッシュテーブル実装のデータ構造です。Goのマップは参照型であり、内部的にはヒープに割り当てられたデータ構造へのポインタとして扱われます。マップを関数に渡すと、マップのヘッダ構造へのポインタがコピーされるため、関数内でマップを変更すると元のマップも変更されます。chan
(チャネル): ゴルーチン間で値を送受信するための通信メカニズムです。Goのチャネルも参照型であり、内部的にはヒープに割り当てられたhchan
構造体へのポインタとして扱われます。チャネルを関数に渡すと、チャネルのヘッダ構造へのポインタがコピーされるため、関数内でチャネルを操作すると元のチャネルも操作されます。string
(文字列): Goの文字列は不変(immutable)なバイト列です。内部的には、バイト列の先頭へのポインタと文字列の長さの2つの要素で構成される構造体として扱われます。文字列を関数に渡す際、このポインタと長さの構造体がコピーされます。文字列自体は不変であるため、コピーによるオーバーヘッドは小さく、元の文字列が変更される心配はありません。しかし、このコミットの時点では、文字列もポインタとして扱われるべきという設計思想があったことが伺えます。- コンパイラの型チェック: コンパイラは、プログラムが言語の型規則に従っているかを検証します。このコミットは、コンパイラの型チェックロジックに新しい制約を追加し、特定の型が特定の形式(ポインタ形式)で宣言されていない場合にエラーを発生させるようにします。
src/cmd/gc/dcl.c
: Goコンパイラのフロントエンドの一部であるgc
(Go Compiler)のソースファイルの一つです。このファイルは主に宣言(declarations)の処理、特に変数の型チェックや初期化に関するロジックを担当しています。
技術的詳細
このコミットは、Goコンパイラのsrc/cmd/gc/dcl.c
ファイル内の型宣言処理に修正を加えています。具体的には、構造体のフィールド宣言時(stotype
関数内)と、変数宣言時(addvar
関数内)において、TCHAN
(チャネル)、TMAP
(マップ)、TSTRING
(文字列)の各型がポインタ形式でない場合にコンパイルエラーを発生させるように変更されています。
Go言語の初期設計では、これらの型は常に参照セマンティクスを持つべきであるという強い意図がありました。これは、これらのデータ構造が動的にサイズ変更されたり、複数の実行コンテキスト(ゴルーチン)間で共有されたりする性質を持つためです。もしこれらの型が値型として扱われた場合、以下のような問題が発生する可能性があります。
- 非効率なコピー:
map
やchan
のような複雑なデータ構造を値としてコピーすると、大量のメモリコピーが発生し、パフォーマンスが著しく低下します。 - セマンティクスの混乱: 値型としてコピーされた
map
やchan
を関数内で変更しても、元の変数には影響が及びません。これは、これらの型が共有されることを期待するプログラマの直感に反し、バグの原因となります。 - 一貫性の欠如:
map
やchan
が値型と参照型の両方で宣言可能だと、言語のセマンティクスが複雑になり、学習コストが増加します。
このコミットは、これらの問題を回避するために、コンパイラレベルでこれらの型が常にポインタとして扱われることを強制します。つまり、ユーザーがvar m map[int]string
のように宣言した場合、コンパイラは内部的にこれをvar m *map[int]string
のようにポインタとして解釈し、その実体はヒープに割り当てられることを保証します。もし、ユーザーが明示的にポインタではない形でこれらの型を宣言しようとした場合、コンパイラはエラーを発生させ、設計意図に沿わないコードを禁止します。
string
型については、Goの文字列は不変であるため、値渡しでも問題ないように見えます。しかし、初期の設計段階では、文字列も内部的にはポインタと長さのペアとして扱われるため、一貫性を持たせるためにポインタ形式を強制した可能性があります。後のGoのバージョンでは、文字列は値型として扱われますが、その内部表現はポインタと長さのペアであるため、実質的には参照渡しに近い効率で動作します。このコミットは、その後の設計変更の過渡期におけるスナップショットを示していると言えます。
コアとなるコードの変更箇所
変更はsrc/cmd/gc/dcl.c
ファイルに集中しています。
-
構造体フィールドの型チェック (
stotype
関数内):n->type->etype
がTCHAN
、TMAP
、TSTRING
のいずれかである場合、yyerror("%T can exist only in pointer form", n->type);
というエラーメッセージを出力し、コンパイルエラーとします。これは、構造体のフィールドとしてこれらの型を直接宣言することを禁止します。--- a/src/cmd/gc/dcl.c +++ b/src/cmd/gc/dcl.c @@ -483,8 +483,18 @@ loop: if(n->op != ODCLFIELD || n->type == T) fatal("stotype: oops %N\\n", n); - if(n->type->etype == TARRAY && n->type->bound < 0) - yyerror("type of a structure field cannot be an open array"); + switch(n->type->etype) { + case TARRAY: + if(n->type->bound < 0) + yyerror("type of a structure field cannot be an open array"); + break; + + case TCHAN: + case TMAP: + case TSTRING: + yyerror("%T can exist only in pointer form", n->type); + break; + }
-
変数宣言の型チェック (
addvar
関数内):t->etype
がTCHAN
、TMAP
、TSTRING
のいずれかである場合、yyerror("%T can exist only in pointer form", t);
というエラーメッセージを出力し、コンパイルエラーとします。これは、通常の変数宣言においてもこれらの型を直接宣言することを禁止します。--- a/src/cmd/gc/dcl.c +++ b/src/cmd/gc/dcl.c @@ -732,6 +742,15 @@ addvar(Node *n, Type *t, int ctxt) pushdcl(s); } + if(t != T) { + switch(t->etype) { + case TCHAN: + case TMAP: + case TSTRING: + yyerror("%T can exist only in pointer form", t); + } + } + redeclare("variable", s); s->vargen = gen; s->oname = n;
コアとなるコードの解説
このコミットの核心は、Goコンパイラが型宣言を処理する際に、特定の型(map
, chan
, string
)に対して追加の制約を課すことです。
dcl.c
ファイルは、Go言語の宣言(変数、関数、型など)を処理するコンパイラの重要な部分です。
stotype
関数は、構造体のフィールドの型をチェックする役割を担っています。この変更により、構造体のフィールドとしてmap
、chan
、string
が直接宣言された場合、コンパイラはエラーを報告します。これは、これらの型が構造体の一部として値渡しされることを防ぎ、常にポインタとして扱われることを強制するためです。
addvar
関数は、新しい変数を宣言する際に呼び出され、その変数の型をチェックします。この変更により、var myMap map[int]string
のような宣言が直接行われた場合、コンパイラはエラーを報告します。これは、これらの型が常にポインタとして扱われるべきであるという設計原則を強制するためです。
エラーメッセージ"%T can exist only in pointer form"
は、これらの型が「ポインタ形式でのみ存在できる」ことを明確に示しています。これは、Go言語の初期段階におけるこれらの型のセマンティクスに関する設計上の決定を直接反映したものです。
現在のGo言語では、map
やchan
は参照型として扱われ、変数に代入したり関数に渡したりすると、その実体へのポインタがコピーされます。しかし、string
は値型として扱われます。このコミットは、Go言語の進化の過程で、これらの型の扱いに関する設計がどのように洗練されていったかを示す貴重な証拠となります。特にstring
に関しては、このコミットの時点ではポインタ形式が強制されていたものの、後に値型として扱われるように変更されたことがわかります。これは、言語設計が常に進化し、初期の決定が後の経験や最適化によって見直される可能性があることを示唆しています。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語のソースコードリポジトリ: https://github.com/golang/go
- Go言語の歴史に関する情報: https://go.dev/blog/go-principles
参考にした情報源リンク
- Go channels are a fundamental part of the Go language's concurrency model: https://go101.org/article/channel.html
- Go channels were designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson: https://en.wikipedia.org/wiki/Go_(programming_language)
- Channels are implemented internally using an
hchan
struct: https://medium.com/@trevor4e/go-channels-internals-75a32291530a - Strings in Go are immutable: https://www.geeksforgeeks.org/strings-in-golang/
- Go言語の初期設計に関するブログ記事や論文 (一般的な情報源)
- Go言語のコンパイラに関する技術文書 (一般的な情報源)
- Go言語の型システムに関する解説 (一般的な情報源)