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

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

このコミットは、Goコンパイラ(gc)にGOEXPERIMENT=os.Errorという実験的なフラグを追加するものです。このフラグを有効にすると、os.Errorが組み込みのerror型へのエイリアスとして扱われるようになります。これは一時的な措置であり、Go言語のエラーハンドリングの進化における特定の移行期間を支援するために導入されました。

コミット

commit 47f4bf763dcb120d3b005974fec848eefe0858f0
Author: Russ Cox <rsc@golang.org>
Date:   Tue Nov 1 23:24:28 2011 -0400

    gc: add GOEXPERIMENT=os.Error
    
    This won't last long, I promise.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/5330066
---
 src/cmd/gc/go.h   |  1 +\
 src/cmd/gc/lex.c  |  1 +\
 src/cmd/gc/subr.c | 11 +++++++++++
 3 files changed, 13 insertions(+)

diff --git a/src/cmd/gc/go.h b/src/cmd/gc/go.h
index 7d6ac08433..cff01a11f8 100644
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -852,6 +852,7 @@ EXTERN	int	typecheckok;
 EXTERN	int	compiling_runtime;
 
 EXTERN	int	rune32;
+EXTERN	int	oserror;
 
 /*
  *\ty.tab.c
diff --git a/src/cmd/gc/lex.c b/src/cmd/gc/lex.c
index 86492a53bc..1dc00d70d3 100644
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -38,6 +38,7 @@ static struct {
 	int *val;
 } exper[] = {
 	{"rune32", &rune32},
+	{"os.Error", &oserror},
 };
 
 static void
diff --git a/src/cmd/gc/subr.c b/src/cmd/gc/subr.c
index dc1d314638..1d5c1aad25 100644
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -2967,6 +2967,17 @@ mkpkg(Strlit *path)\n 	p->prefix = pathtoprefix(path->s);\n 	p->link = phash[h];\n 	phash[h] = p;\n+\t\n+\t// If the compiler was built with\n+\t//	GOEXPERIMENT=os.Error\n+\t// define os.Error as an alias for error.\n+\t// Terrible and won't last long, but useful for transitions.\n+\tif(oserror && strcmp(path->s, \"os\") == 0) {\n+\t\tSym *s;\n+\t\ts = pkglookup(\"Error\", p);\n+\t\ts->def = typenod(errortype);\n+\t}\n+\n 	return p;\n }\n \n```

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

[https://github.com/golang/go/commit/47f4bf763dcb120d3b005974fec848eefe0858f0](https://github.com/golang/go/commit/47f4bf763dcb120d3b005974fec848eefe0858f0)

## 元コミット内容

Goコンパイラ(`gc`)に`GOEXPERIMENT=os.Error`という実験的なビルドフラグを追加します。このフラグは、`os.Error`を`error`型へのエイリアスとして定義するために使用されます。コミットメッセージには「これは長くは続かない、約束する」と明記されており、一時的な互換性または移行のための機能であることが示唆されています。

## 変更の背景

Go言語の初期のバージョンでは、エラーハンドリングのメカニズムが現在とは異なる形であった可能性があります。Goのエラーハンドリングは「エラーは値である」という哲学に基づいており、関数は成功時には`nil`、エラー時には非`nil`の`error`インターフェース型の値を返します。

このコミットが作成された2011年11月という時期は、Go言語がまだ活発に開発され、言語仕様や標準ライブラリが固まりつつあった時期にあたります。`os.Error`という記述は、おそらく`os`パッケージ内で定義されていた特定のエラー型、あるいはエラーを表現するための慣習的な型名であったと考えられます。しかし、Go言語の設計思想として、エラーは特定のパッケージに依存する具象型ではなく、汎用的な`error`インターフェースとして扱うべきであるという方向性が確立されていきました。

このコミットは、既存のコードベースやユーザーが`os.Error`を使用している状況から、より汎用的な`error`インターフェースへの移行を円滑にするための一時的な措置として導入されたと推測されます。`GOEXPERIMENT`フラグを使用することで、この変更が標準的な動作ではないこと、そして将来的に削除される予定であることが明確に示されています。これは、Go言語の進化の過程で、後方互換性を保ちつつ、より良い設計へと移行するための工夫の一つと言えます。

## 前提知識の解説

### Go言語のエラーハンドリング

Go言語では、エラーは組み込みの`error`インターフェースによって表現されます。このインターフェースは非常にシンプルで、`Error() string`という単一のメソッドのみを持ちます。
```go
type error interface {
    Error() string
}

関数がエラーを返す場合、通常は戻り値の最後の要素としてerror型を返します。エラーがない場合はnilを返します。呼び出し側はif err != nilという慣用句を使ってエラーの有無をチェックします。

GOEXPERIMENT環境変数

GOEXPERIMENTは、Goツールチェインにおける実験的な機能や変更を有効にするための環境変数です。これは、まだ標準のGoリリースには含まれていないが、開発チームがテストやフィードバックのために早期に利用可能にしたい機能に対して使用されます。GOEXPERIMENTで有効化された機能は、将来のGoのバージョンで正式に導入されることもあれば、破棄されることもあります。このコミットのように、一時的な互換性レイヤーを提供するためにも使用されることがあります。

Goコンパイラ(gc

gcは、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイラの内部では、言語の構文解析、型チェック、最適化、コード生成などが行われます。このコミットは、コンパイラの内部動作、特に型システムとシンボル解決の層に手を入れることで、特定の型名(os.Error)の解釈を変更しています。

src/cmd/gcディレクトリ

Goのソースコードリポジトリにおいて、src/cmd/gcはGoコンパイラ(gc)のソースコードが格納されているディレクトリです。このディレクトリ内のファイルは、コンパイラの様々な部分(字句解析、構文解析、型チェック、コード生成など)を実装しています。

  • go.h: コンパイラのグローバルな定義や外部変数宣言が含まれるヘッダーファイルです。
  • lex.c: 字句解析器(lexer)の実装が含まれます。ソースコードをトークンに分割する役割を担います。GOEXPERIMENTフラグの認識もここで行われます。
  • subr.c: サブルーチンやユーティリティ関数が含まれます。パッケージの解決や型の定義など、コンパイラの様々な補助的な処理が行われます。

技術的詳細

このコミットの技術的な核心は、Goコンパイラがos.Errorというシンボルをどのように解決するかを変更することにあります。通常、os.Errorosパッケージ内で定義されたErrorという名前の型を指します。しかし、この変更により、GOEXPERIMENT=os.Errorが有効な場合、コンパイラはos.Errorerrorインターフェースのエイリアスとして扱います。

具体的には、以下のステップでこのエイリアスが実現されます。

  1. oserrorフラグの導入: src/cmd/gc/go.hoserrorという新しいグローバル変数が追加されます。この変数は、GOEXPERIMENT=os.Errorが有効かどうかを示すフラグとして機能します。
  2. GOEXPERIMENTの認識: src/cmd/gc/lex.cexper配列に"os.Error"という文字列と&oserrorへのポインタが追加されます。これにより、コンパイラの起動時にGOEXPERIMENT=os.Errorが指定された場合、oserror変数がtrueに設定されます。
  3. os.Errorのエイリアス定義: src/cmd/gc/subr.cmkpkg関数(パッケージのシンボルを解決する際に呼び出される可能性のある関数)内で、oserrorフラグがtrueであり、かつ現在処理しているパッケージが"os"である場合に特別な処理が追加されます。この処理では、osパッケージ内の"Error"というシンボルを検索し、その定義を組み込みのerror型(errortype)に設定します。

これにより、コンパイラはosパッケージのErrorという名前の型を、あたかもそれが組み込みのerror型であるかのように扱います。これは、ソースコード内でos.Errorと記述されていても、コンパイル時にはそれがerrorインターフェースとして解釈されることを意味します。

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

diff --git a/src/cmd/gc/go.h b/src/cmd/gc/go.h
index 7d6ac08433..cff01a11f8 100644
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -852,6 +852,7 @@ EXTERN	int	typecheckok;
 EXTERN	int	compiling_runtime;
 
 EXTERN	int	rune32;
+EXTERN	int	oserror;
 
 /*
  *\ty.tab.c
diff --git a/src/cmd/gc/lex.c b/src/cmd/gc/lex.c
index 86492a53bc..1dc00d70d3 100644
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -38,6 +38,7 @@ static struct {
 	int *val;
 } exper[] = {
 	{"rune32", &rune32},
+	{"os.Error", &oserror},
 };
 
 static void
diff --git a/src/cmd/gc/subr.c b/src/cmd/gc/subr.c
index dc1d314638..1d5c1aad25 100644
--- a/src/cmd/gc/subr.c
+++ b/cmd/gc/subr.c
@@ -2967,6 +2967,17 @@ mkpkg(Strlit *path)\n 	p->prefix = pathtoprefix(path->s);\n 	p->link = phash[h];\n 	phash[h] = p;\n+\t\n+\t// If the compiler was built with\n+\t//	GOEXPERIMENT=os.Error\n+\t// define os.Error as an alias for error.\n+\t// Terrible and won't last long, but useful for transitions.\n+\tif(oserror && strcmp(path->s, \"os\") == 0) {\n+\t\tSym *s;\n+\t\ts = pkglookup(\"Error\", p);\n+\t\ts->def = typenod(errortype);\n+\t}\n+\n 	return p;\n }\

コアとなるコードの解説

src/cmd/gc/go.h

EXTERN	int	oserror;

oserrorという名前の整数型外部変数が宣言されています。EXTERNキーワードは、この変数が他のファイルで定義されていることを示します。この変数は、GOEXPERIMENT=os.Errorが有効であるかどうかを示すフラグとして使用されます。

src/cmd/gc/lex.c

static struct {
	int *val;
} exper[] = {
	{"rune32", &rune32},
	{"os.Error", &oserror},
};

experという静的構造体配列に新しいエントリが追加されています。この配列は、GOEXPERIMENT環境変数で指定できる実験的なフラグとそのフラグに対応する変数のアドレスをマッピングしています。 {"os.Error", &oserror}というエントリは、GOEXPERIMENTos.Errorが指定された場合、oserror変数の値が設定されるようにします。これにより、コンパイラはos.Error実験が有効であることを内部的に認識できます。

src/cmd/gc/subr.c

 	// If the compiler was built with
 	//	GOEXPERIMENT=os.Error
 	// define os.Error as an alias for error.
 	// Terrible and won't last long, but useful for transitions.
 	if(oserror && strcmp(path->s, "os") == 0) {
 		Sym *s;
 		s = pkglookup("Error", p);
 		s->def = typenod(errortype);
 	}

mkpkg関数内に新しい条件分岐が追加されています。mkpkgは、Goのパッケージがコンパイラによって処理される際に呼び出される関数の一つです。

  • if(oserror && strcmp(path->s, "os") == 0): この条件は、以下の2つの条件が両方とも真である場合にブロック内のコードを実行します。
    • oserror: GOEXPERIMENT=os.Errorフラグが有効であること。
    • strcmp(path->s, "os") == 0: 現在処理しているパッケージのパスが"os"であること。
  • Sym *s;: Sym型のポインタsを宣言します。Symはコンパイラ内部でシンボル(変数名、型名など)を表す構造体です。
  • s = pkglookup("Error", p);: osパッケージ(p)内で"Error"という名前のシンボルを検索し、そのシンボルへのポインタをsに代入します。
  • s->def = typenod(errortype);: 検索した"Error"シンボルの定義(defフィールド)を、組み込みのerror型を表すノード(typenod(errortype))に設定します。

このコードブロックの目的は、GOEXPERIMENT=os.Errorが有効な場合に、osパッケージ内のErrorという名前の型を、Goの組み込みerrorインターフェースのエイリアスとして扱うようにコンパイラに指示することです。これにより、os.Errorを使用している既存のコードが、errorインターフェースを期待する新しいコードと互換性を持つようになります。コメントにあるように、これは一時的な「ひどい」解決策であり、移行期間のために役立つとされています。

関連リンク

参考にした情報源リンク