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

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

このコミットでは、Goコンパイラのランタイム部分、特に型アサーションとインターフェースの扱いに関連するファイルが変更されています。具体的には以下の3つのファイルが修正されました。

  • src/cmd/gc/go.h: Goコンパイラのグローバルヘッダーファイルで、型定義や関数プロトタイプが宣言されています。インターフェース関連の関数のシグネチャが変更されています。
  • src/cmd/gc/subr.c: Goコンパイラのサブルーチンが含まれるファイルで、型チェックやインターフェースの適合性チェックに関するロジックが実装されています。このコミットの主要な変更点が含まれています。
  • src/cmd/gc/walk.c: GoコンパイラのAST (Abstract Syntax Tree) ウォーカーが含まれるファイルで、コードの変換や型チェックの際にASTを走査します。インターフェース関連の関数呼び出しが更新されています。

コミット

commit 5f4f5647efbea27b90ffc034e931082f843e6333
Author: Russ Cox <rsc@golang.org>
Date:   Wed Feb 11 17:57:29 2009 -0800

    require type assertions when narrowing.
    
    R=ken
    OCL=24350
    CL=24914

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

https://github.com/golang/go/commit/5f4f5647efbea27b90ffc034e931082f843e6333

元コミット内容

require type assertions when narrowing.

R=ken
OCL=24350
CL=24914

変更の背景

このコミットは、Go言語における型アサーションの振る舞いをより厳密にするための変更です。特に、インターフェース型から具体的な型へ、あるいはより狭いインターフェース型へと「ナローイング(絞り込み)」を行う際に、明示的な型アサーションを要求するようにコンパイラのチェックを強化しています。

Go言語の初期設計段階において、インターフェースの柔軟性と型安全性のバランスは重要な検討事項でした。インターフェースは異なる具象型を抽象的に扱う強力なメカニズムですが、その内部に保持されている具体的な値の型を知る必要がある場合、型アサーション (value.(Type)) を使用します。

このコミット以前は、コンパイラが暗黙的に型をナローイングするような状況、例えば、あるインターフェース型が別のインターフェース型に代入される際に、そのインターフェースが持つメソッドセットが部分的にしか満たされていない場合でも、エラーが報告されにくいケースがあったと考えられます。この変更は、このような暗黙的なナローイングを許容せず、開発者が意図的に型を絞り込む場合にのみ、明示的な型アサーションを記述することを強制することで、より堅牢なコードを記述できるようにすることを目的としています。これにより、コンパイル時に潜在的な型不一致エラーを早期に発見し、実行時エラーのリスクを低減します。

前提知識の解説

Go言語のインターフェース

Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは、JavaやC#のような明示的なimplementsキーワードを必要とせず、型がインターフェースで定義されたすべてのメソッドを実装していれば、そのインターフェースを満たすと見なされます(構造的型付け)。

例:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}

型アサーション (Type Assertion)

型アサーションは、インターフェース型の変数が保持している基になる具体的な値の型を検査し、その値にアクセスするために使用されます。

構文: t := i.(T)

  • i はインターフェース型の変数。
  • T は検査したい型(具象型または別のインターフェース型)。

型アサーションには2つの形式があります。

  1. 単一の値の型アサーション: t := i.(T)
    • iT 型の値を保持していない場合、パニック (panic) が発生します。
  2. 「カンマOK」イディオム (Comma-ok idiom): t, ok := i.(T)
    • iT 型の値を保持している場合、t にその値が代入され、oktrue になります。
    • iT 型の値を保持していない場合、tT 型のゼロ値になり、okfalse になります。パニックは発生しません。この形式が推奨されます。

型のナローイング (Type Narrowing)

型のナローイングとは、より広い(汎用的な)型から、より狭い(具体的な)型へと、変数の型を絞り込むプロセスを指します。Go言語では、インターフェース型から具象型への変換や、より広いインターフェース型からより狭いインターフェース型への変換がこれに該当します。

例えば、interface{}(空インターフェース、任意の型を保持できる)から string 型への変換はナローイングです。また、ReadWriter インターフェースから Reader インターフェースへの変換は、メソッドセットが広がる方向なのでナローイングではありませんが、Reader から ReadWriter への変換は、メソッドセットが狭まる方向なのでナローイングと見なされる場合があります(このコミットの文脈では、インターフェース間の適合性チェックがより厳密になったことを指します)。

このコミットの「narrowing」という言葉は、特にインターフェースの適合性チェックにおいて、より具体的な型への変換や、メソッドセットがより厳密に一致する必要がある状況を指しています。

技術的詳細

このコミットの主要な変更は、Goコンパイラのインターフェース適合性チェックロジックに、明示的な型アサーションが必要かどうかを示すexplicitという新しいフラグを導入した点です。

src/cmd/gc/go.h の変更

ifaceasifaceas1ifacecheck の3つの関数プロトタイプに int explicit という新しい引数が追加されました。

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -827,9 +827,9 @@ Type*	fixchan(Type*);
 Node*	chanop(Node*, int);
 Node*	arrayop(Node*, int);
 Node*	ifaceop(Type*, Node*, int);
-int	ifaceas(Type*, Type*);
-int	ifaceas1(Type*, Type*);
-void	ifacecheck(Type*, Type*, int);
+int	ifaceas(Type*, Type*, int);
+int	ifaceas1(Type*, Type*, int);
+void	ifacecheck(Type*, Type*, int, int);
 void	runifacechecks(void);
 Node*	convas(Node*);
 void	arrayconv(Type*, Node*);
  • ifaceas: インターフェース間の型変換の可能性をチェックする関数。
  • ifaceas1: ifaceas の内部で使われる、より低レベルなインターフェース型変換チェック関数。
  • ifacecheck: インターフェースの適合性チェックをキューに入れる関数。

この explicit 引数は、その型変換がコード内で明示的に記述された型アサーションによるものか(explicit1)、それとも暗黙的な型変換の文脈で発生したものか(explicit0)を示します。

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

このファイルでは、ifacecheck 関数の定義が変更され、Icheck 構造体に explicit フィールドが追加されました。また、インターフェース適合性チェックの主要なロジックである runifacechecks 関数が大幅に修正されました。

  1. Icheck 構造体の変更: Icheck 構造体は、後で実行されるインターフェース適合性チェックの情報を保持します。ここに int explicit; フィールドが追加され、チェックが明示的なアサーションによるものかどうかの情報が保存されるようになりました。

    --- a/src/cmd/gc/subr.c
    +++ b/src/cmd/gc/subr.c
    @@ -2735,12 +2735,13 @@ struct Icheck
     	Type *dst;
     	Type *src;
     	int lineno;
    +	int explicit;
     };
     Icheck *icheck;
     Icheck *ichecktail;
    
     void
    -ifacecheck(Type *dst, Type *src, int lineno)
    +ifacecheck(Type *dst, Type *src, int lineno, int explicit)
     {
     	Icheck *p;
    
    @@ -2752,6 +2753,7 @@ ifacecheck(Type *dst, Type *src, int lineno)
     	p->dst = dst;
     	p->src = src;
     	p->lineno = lineno;
    +	p->explicit = explicit;
     	ichecktail = p;
     }
    
  2. ifaceokT2I および ifaceokI2I 関数の追加/変更:

    • hasiface 関数が ifaceokT2I にリネームされ、非インターフェース型 t がインターフェース型 iface を満たすかどうかをチェックするようになりました。
    • ifaceokI2I 関数が新しく追加され、インターフェース型 i1 が別のインターフェース型 i2 を満たすかどうかをチェックするようになりました。これは、インターフェースのメソッドセットの包含関係をチェックします。
    --- a/src/cmd/gc/subr.c
    +++ b/src/cmd/gc/subr.c
    @@ -2761,6 +2763,9 @@ ifacelookdot(Sym *s, Type *t)
     	int c, d;
     	Type *m;
    
    +	if(t == T)
    +		return T;
    +
     	for(d=0; d<nelem(dotlist); d++) {
     		c = adddot1(s, t, d, &m);
     		if(c > 1) {
    @@ -2773,15 +2778,15 @@ ifacelookdot(Sym *s, Type *t)
     	return T;
     }
    
    +// check whether non-interface type t
    +// satisifes inteface type iface.
     int
    -hasiface(Type *t, Type *iface, Type **m)
    +ifaceokT2I(Type *t, Type *iface, Type **m)
     {
     	Type *im, *tm;
     	int imhash;
    
     	t = methtype(t);
    -	if(t == T)
    -		return 0;
    
     	// if this is too slow,
     	// could sort these first
    @@ -2805,26 +2810,66 @@ hasiface(Type *t, Type *iface, Type **m)
     	return 1;
     }
    
    +// check whether interface type i1 satisifes interface type i2.
    +int
    +ifaceokI2I(Type *i1, Type *i2, Type **m)
    +{
    +	Type *m1, *m2;
    +
    +	// if this is too slow,
    +	// could sort these first
    +	// and then do one loop.
    +
    +	for(m2=i2->type; m2; m2=m2->down) {
    +		for(m1=i1->type; m1; m1=m1->down)
    +			if(m1->sym == m2->sym && typehash(m1, 0) == typehash(m2, 0))
    +				goto found;
    +		*m = m2;
    +		return 0;
    +	found:;
    +	}
    +	return 1;
    +}
    
  3. runifacechecks 関数のロジック変更: この関数は、コンパイル中にキューに入れられたすべてのインターフェース適合性チェックを実行します。変更後、この関数は explicit フラグを考慮し、エラーメッセージをより詳細に生成するようになりました。

    • p->explicit0 (暗黙的変換) で needexplicit1 (明示的変換が必要) の場合、yyerror("need explicit conversion...") というエラーを出力します。これは、コンパイラが暗黙的に型をナローイングしようとしたが、それが許可されない場合に発生します。
    • wrong1 の場合(型が適合しない場合)、yyerror("%T is not %T...") というエラーを出力します。
    --- a/src/cmd/gc/subr.c
    +++ b/src/cmd/gc/subr.c
    @@ -2810,26 +2855,40 @@ runifacechecks(void)
      void
      runifacechecks(void)
      {
      	Icheck *p;
    -	int lno;
    -	Type *m, *l, *r;
    +	int lno, wrong, needexplicit;
    +	Type *m, *t, *iface;
    
      	lno = lineno;
      	for(p=icheck; p; p=p->next) {
      		lineno = p->lineno;
    -		if(isinter(p->dst)) {
    -			l = p->src;
    -			r = p->dst;
    +		wrong = 0;
    +		needexplicit = 0;
    +		m = nil;
    +		if(isinter(p->dst) && isinter(p->src)) {
    +			iface = p->dst;
    +			t = p->src;
    +			needexplicit = !ifaceokI2I(t, iface, &m);
    +		}
    +		else if(isinter(p->dst)) {
    +			t = p->src;
    +			iface = p->dst;
    +			wrong = !ifaceokT2I(t, iface, &m);
      		} else {
    -			l = p->dst;
    -			r = p->src;
    +			t = p->dst;
    +			iface = p->src;
    +			wrong = !ifaceokT2I(t, iface, &m);
    +			needexplicit = 1;
    +		}
    +		if(wrong)
    +			yyerror("%T is not %T\n\tmissing %S%hhT",
    +				t, iface, m->sym, m->type);
    +		else if(!p->explicit && needexplicit) {
    +			if(m)
    +				yyerror("need explicit conversion to use %T as %T\n\tmissing %S%hhT",
    +					p->src, p->dst, m->sym, m->type);
    +			else
    +				yyerror("need explicit conversion to use %T as %T",
    +					p->src, p->dst);
      		}
    -		if(!hasiface(l, r, &m))
    -			yyerror("%T is not %T - missing %S%hhT",
    -				l, r, m->sym, m->type);
      	}
      	lineno = lno;
      }
    

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

このファイルでは、ifaceas および ifaceas1 関数の呼び出し箇所に、新しい explicit 引数として 1 または 0 が渡されるようになりました。

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -513,7 +513,7 @@ loop:
 			walktype(r->left, Erv);
 			if(r->left == N)
 				break;
-			et = ifaceas1(r->type, r->left->type);
+			et = ifaceas1(r->type, r->left->type, 1);
 			switch(et) {
 			case I2T:
 				et = I2T2;
@@ -651,7 +651,7 @@ loop:
 		}

 		// interface assignment
-		et = ifaceas(n->type, l->type);
+		et = ifaceas(n->type, l->type, 1);
 		if(et != Inone) {
 			indir(n, ifaceop(n->type, l, et));
 			goto ret;
@@ -2812,7 +2812,7 @@ arrayop(Node *n, int top)
  * return op to use.
  */
 int
-ifaceas1(Type *dst, Type *src)
+ifaceas1(Type *dst, Type *src, int explicit)
 {
 	if(src == T || dst == T)
 		return Inone;
@@ -2821,17 +2821,17 @@ ifaceas1(Type *dst, Type *src)
 		if(isinter(src)) {
 			if(eqtype(dst, src, 0))
 				return I2Isame;
+			if(!isnilinter(dst))
+				ifacecheck(dst, src, lineno, explicit);
 			return I2I;
 		}
 		if(isnilinter(dst))
 			return T2I;
-		ifacecheck(dst, src, lineno);
+		ifacecheck(dst, src, lineno, explicit);
 		return T2I;
 	}
 	if(isinter(src)) {
-		if(isnilinter(src))
-			return I2T;
-		ifacecheck(dst, src, lineno);
+		ifacecheck(dst, src, lineno, explicit);
 		return I2T;
 	}
 	return Inone;
@@ -2841,11 +2841,11 @@ ifaceas1(Type *dst, Type *src)
  * treat convert T to T as noop
  */
 int
-ifaceas(Type *dst, Type *src)
+ifaceas(Type *dst, Type *src, int explicit)
 {
 	int et;

-	et = ifaceas1(dst, src);
+	et = ifaceas1(dst, src, explicit);
 	if(et == I2Isame)
 		et = Inone;
 	return et;
@@ -2987,7 +2987,7 @@ convas(Node *n)\n 	if(eqtype(lt, rt, 0))\n 		goto out;\n\n-	et = ifaceas(lt, rt);\n+	et = ifaceas(lt, rt, 0);\n 	if(et != Inone) {\n 		n->right = ifaceop(lt, r, et);\n 		goto out;\n```
*   `ifaceas1` の呼び出しでは、明示的な型アサーションの文脈であるため `explicit` に `1` が渡されています。
*   `ifaceas` の呼び出しでは、代入などの暗黙的な変換の文脈であるため `explicit` に `0` が渡されています。

これらの変更により、コンパイラは型変換の際に、それが明示的なアサーションによるものか、暗黙的な代入によるものかを区別できるようになり、暗黙的なナローイングに対してより厳密なチェックを適用することが可能になりました。

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

### `src/cmd/gc/subr.c` における `runifacechecks` 関数の変更

```c
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -2810,26 +2855,40 @@ runifacechecks(void)
  void
  runifacechecks(void)
  {
  	Icheck *p;
-	int lno;
-	Type *m, *l, *r;
+	int lno, wrong, needexplicit;
+	Type *m, *t, *iface;

  	lno = lineno;
  	for(p=icheck; p; p=p->next) {
  		lineno = p->lineno;
-		if(isinter(p->dst)) {
-			l = p->src;
-			r = p->dst;
+		wrong = 0;
+		needexplicit = 0;
+		m = nil;
+		if(isinter(p->dst) && isinter(p->src)) {
+			iface = p->dst;
+			t = p->src;
+			needexplicit = !ifaceokI2I(t, iface, &m);
+		}
+		else if(isinter(p->dst)) {
+			t = p->src;
+			iface = p->dst;
+			wrong = !ifaceokT2I(t, iface, &m);
  		} else {
-			l = p->dst;
-			r = p->src;
+			t = p->dst;
+			iface = p->src;
+			wrong = !ifaceokT2I(t, iface, &m);
+			needexplicit = 1;
+		}
+		if(wrong)
+			yyerror("%T is not %T\n\tmissing %S%hhT",
+				t, iface, m->sym, m->type);
+		else if(!p->explicit && needexplicit) {
+			if(m)
+				yyerror("need explicit conversion to use %T as %T\n\tmissing %S%hhT",
+					p->src, p->dst, m->sym, m->type);
+			else
+				yyerror("need explicit conversion to use %T as %T",
+					p->src, p->dst);
  		}
-		if(!hasiface(l, r, &m))
-			yyerror("%T is not %T - missing %S%hhT",
-				l, r, m->sym, m->type);
  	}
  	lineno = lno;
  }

コアとなるコードの解説

runifacechecks 関数は、コンパイラが収集したすべてのインターフェース適合性チェックを最終的に実行する場所です。この変更により、チェックのロジックが大幅に洗練されました。

  1. 型変換の分類:

    • if(isinter(p->dst) && isinter(p->src)): 変換元と変換先が両方ともインターフェース型の場合。この場合、ifaceokI2I を使用してインターフェース間の適合性をチェックし、明示的な変換が必要かどうか (needexplicit) を判断します。
    • else if(isinter(p->dst)): 変換先がインターフェース型で、変換元が非インターフェース型の場合(例: 具象型からインターフェース型への変換)。ifaceokT2I を使用して適合性をチェックし、適合しない場合は wrong フラグを立てます。
    • else: 変換元がインターフェース型で、変換先が非インターフェース型の場合(例: インターフェース型から具象型への変換)。これも ifaceokT2I を使用して適合性をチェックし、適合しない場合は wrong フラグを立てます。このケースでは常に明示的な変換が必要 (needexplicit = 1) と見なされます。
  2. エラーメッセージの生成:

    • if(wrong): 型が適合しない場合、yyerror("%T is not %T...") というエラーメッセージを出力します。これは、型アサーションが失敗した場合や、インターフェースがメソッドを満たしていない場合に表示されます。
    • else if(!p->explicit && needexplicit): ここがこのコミットの核心です。
      • !p->explicit: チェックが明示的な型アサーションによるものではない(つまり、暗黙的な変換の文脈である)ことを意味します。
      • needexplicit: しかし、この変換には明示的な型アサーションが必要であると判断された場合。
      • この両方の条件が真の場合、yyerror("need explicit conversion to use %T as %T...") というエラーメッセージが出力されます。これは、コンパイラが暗黙的に型をナローイングしようとしたが、それが許可されず、開発者に明示的な型アサーションの記述を促すものです。

このロジックの変更により、Goコンパイラは、インターフェースの適合性チェックにおいて、より厳密なルールを適用し、特にインターフェースから具象型への変換や、より狭いインターフェース型への変換において、明示的な型アサーションを強制するようになりました。これにより、開発者はコードの意図をより明確に表現し、コンパイル時に潜在的な型関連のエラーを捕捉できるようになります。

関連リンク

参考にした情報源リンク

  • stackoverflow.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHqsqb-Ze2L1asDXL1asDXLtkXz2MoV1UrcB5rTRGbAxBfGXwtOkqO2tsnhhELs9_kWXlgaGT2bk1I6Q_-pLZ6vGM4cyJKqmK33v1Oxeq0V_IbGxtv3Hq4VojcA1JsyTIaLltGWlxBVlXDtvp9a9KbfJgWRiZf3b7PKRuRM3kBxcytLNjvqhl7Gr8)
  • devgenius.io (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF3KyIKke_L48YQ5SjeJcImignB4TrlxrWwvbU3BCa5HLKJVJ3WjrBfzxWkDJy2CJABAb7f0jB4d7E--hZmBxnB2slpcQ9e8efnLMr1XcV_alTKhxkuQqM2Cdp3FlTFj3WCMDHQTSW1TBNJepy7X9a3JAph8lY7ASBQUwwv39kKomA_LcaWGUxS2LYGMusU6FCGM5OX-zExw==)
  • geeksforgeeks.org (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEypLJoOB3rcWE-qIBKeI2SeQ_5EwE99rQzVxcNCMNA0kZ6bFMFhr3J_znPiU9VXCdhh5cXBpuv2WP9zUycKCxWj6_aqtL9ydKBr62ZQ6FzTRE_HHWHQiw2JlASO8b5KJv_UUmgICvjdupoDpDSkFJYNqrrQ-hIiq-tI82oFa3w6QAs1bKqUGGb)
  • medium.com (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE2QcwTIkQqwjFUSar1dmX6eX3UP0mGpkNf_M6zNowkcZ--7IAVJ6iu8ljWAN3o1oOP1juZQL9GO-VH70qejsdjky7SlyGQZ0ZPI-h7BR4M0tkJKXtahBN5u9y7_tCMYw6QXN2QghK0dtidpnuEMaEwzc4SaotxwOgpl14Ekf4ksC-_BZliSF7OoeSXBT7o5Em-aHB6AKqDkZA_tAryej0=)
  • labex.io (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFAIi2P8v-_L0ao66vJg1IH3MSh8-oFJAqflFoIBja-LAfx3RITlYQNAMTLo5Vv69mpgp6g6AZDgBfs3Jsa4ItIW0NcsWE5M0VDW18c5wz-Dere0-uG7t0QgMP9Vot-y14plo8zttapSBWnCykqGmukfL2Rvlo0zYnfnBrapz58qCoKwKhwtK9xYp5_Y7ofQwF1EtXK)
  • golang.org (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF0wkmrCNRSedNcd74DedxKBCmrr7R0sb9mBh5ior3upN--_aves_n8jC6rigkDf5PG7cKYCM0rWBw5SBXotnOH7vH3SMjLZRxq__kAeo_BnAxnHnt-U6osUHc=)
  • go.dev (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQELgicZ6F0DHlNoi47oOF9IJ6Q0QNOIYC4E8R5470gNig2-P_h_eJWYdwzpAcHFua7aPsV551IC6ijX0XYnOmGhsQiF9IQtUZmnZMUrRL5GFB2EBzabXLbSUg==)
  • wikipedia.org (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHFANmfo11n37TzNhcC2jGkqcrDnTR-iy_6zbq5NbKAPhovy41Z7erfw8LdS3zhSjZ1KVJrUjZXrZ_pVePxnKNiByeWCTmeEl5XkuLTJUBu-o6n_YLmo11Bh0pbaB89DnY4PolL-U-mtx0HFwACVw==)