[インデックス 17239] ファイルの概要
このコミットは、Go言語のランタイムにおける runtime.SetFinalizer
関数の挙動を変更し、ファイナライザ関数の引数としてより柔軟な型を許容するように拡張するものです。具体的には、ファイナライズされるオブジェクトの型が、ファイナライザ関数の引数に「代入可能」であれば、そのファイナライザが有効であると判断されるようになります。これにより、Goの通常の型システムにおける代入規則と SetFinalizer
の挙動が一貫したものになります。
コミット
commit 5822e7848a5c355f694c22dce4a1da43f2793441
Author: Russ Cox <rsc@golang.org>
Date: Wed Aug 14 14:54:31 2013 -0400
runtime: make SetFinalizer(x, f) accept any f for which f(x) is valid
Originally the requirement was f(x) where f's argument is
exactly x's type.
CL 11858043 relaxed the requirement in a non-standard
way: f's argument must be exactly x's type or interface{}.
If we're going to relax the requirement, it should be done
in a way consistent with the rest of Go. This CL allows f's
argument to have any type for which x is assignable;
that's the same requirement the compiler would impose
if compiling f(x) directly.
Fixes #5368.
R=dvyukov, bradfitz, pieter
CC=golang-dev
https://golang.org/cl/12895043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5822e7848a5c355f694c22dce4a1da43f2793441
元コミット内容
ランタイム: SetFinalizer(x, f)
が f(x)
が有効である任意の f
を受け入れるようにする
元々、f
の引数は x
の型と完全に一致する必要がありました。
CL 11858043 は、非標準的な方法で要件を緩和しました: f
の引数は x
の型か interface{}
でなければなりませんでした。
もし要件を緩和するのであれば、Goの他の部分と一貫した方法で行われるべきです。このCLは、f
の引数が x
が代入可能である任意の型を持つことを許可します。これは、コンパイラが f(x)
を直接コンパイルする場合に課すのと同じ要件です。
Issue #5368 を修正します。
変更の背景
Go言語の runtime.SetFinalizer
関数は、特定のオブジェクトがガベージコレクションによって回収される直前に実行される関数(ファイナライザ)を設定するために使用されます。このファイナライザ関数は、ファイナライズされるオブジェクトを引数として受け取ります。
このコミット以前の SetFinalizer
の挙動には、以下のような経緯と問題点がありました。
-
初期の厳格な型要件:
SetFinalizer(x, f)
を呼び出す際、ファイナライザ関数f
の唯一の引数は、ファイナライズされるオブジェクトx
の型と「完全に一致」する必要がありました。例えば、*int
型のオブジェクトに対してファイナライザを設定する場合、ファイナライザ関数もfunc(*int)
のシグネチャを持つ必要がありました。これは非常に厳格であり、Goの柔軟な型システム(特にインターフェースや型変換)とは整合性が低いものでした。 -
CL 11858043 による非標準的な緩和: 以前のコミット(CL 11858043)では、この要件が緩和され、ファイナライザ関数の引数が
x
の型、またはinterface{}
であれば許容されるようになりました。これは一見すると柔軟性をもたらすように見えますが、interface{}
以外の任意のインターフェース型や、x
の型に代入可能な他の具体的な型(例えば、*int
を受け取るファイナライザにTintptr
型のポインタを渡す場合など)を許容しないという点で、Goの通常の型システムにおける代入規則とは異なる、特殊なルールとなっていました。 -
Issue #5368 の発生: この非標準的な緩和が原因で、Issue #5368 "SetFinalizer should allow finalizer argument to be assignable" が報告されました。このIssueは、
SetFinalizer
がGoの通常の代入規則に従うべきであり、ファイナライザの引数がオブジェクトの型に代入可能であれば受け入れるべきだと主張していました。例えば、*int
型のオブジェクトに対してfunc(interface{})
だけでなく、func(Tintptr)
(ここでtype Tintptr *int
) のようなファイナライザも設定できるようにすべきだという要望です。
このコミットは、上記の背景を踏まえ、SetFinalizer
の型チェックロジックをGoのコンパイラが課す通常の代入規則と一致させることで、より直感的で一貫性のあるAPIを提供することを目的としています。これにより、開発者は SetFinalizer
を使用する際に、Goの他の部分と同じ型推論と代入のルールを期待できるようになります。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の概念とランタイムの内部構造に関する知識が役立ちます。
-
runtime.SetFinalizer
:- Goの
runtime
パッケージが提供する関数で、オブジェクトがガベージコレクタによってメモリから解放される直前に実行される関数(ファイナライザ)を設定します。 - シグネチャは
func SetFinalizer(obj interface{}, finalizer interface{})
です。 obj
はポインタ型である必要があります(例:new(int)
や構造体リテラルのアドレス)。finalizer
はobj
を引数として受け取る関数である必要があります。- ファイナライザは、リソースのクリーンアップ(ファイルハンドルのクローズ、ネットワーク接続の切断など)に利用されることがありますが、その実行タイミングはGCに依存するため、確実なリソース管理には
defer
や明示的なクローズ関数が推奨されます。
- Goの
-
Goのインターフェース:
- Goのインターフェースは、メソッドのシグネチャの集合を定義する型です。
- 空インターフェース (
interface{}
): 任意の型の値を保持できる特別なインターフェースです。Goの全ての型はinterface{}
を実装します。 - 非空インターフェース: 1つ以上のメソッドシグネチャを持つインターフェースです。特定のメソッドを実装する型のみがそのインターフェースを満たします。
- 型アサーションと型変換: Goでは、インターフェース値が保持する具体的な型を
value.(Type)
の形式でアサートしたり、異なる型間で変換したりすることができます。このコミットでは、ランタイム内部でオブジェクトの型をファイナライザの引数型に変換するロジックが重要になります。
-
Goの型システムにおける代入規則:
- Goでは、ある型の値が別の型の変数に代入できるかどうかは、厳密な規則に基づいています。
- 具体的な型からインターフェース型への代入: 具体的な型がインターフェースの全てのメソッドを実装していれば、そのインターフェース型に代入可能です。
- ポインタ型:
*T
型はT
型のポインタを表します。*T
からinterface{}
への代入は可能ですが、*T
から*U
への代入は、T
とU
が同じ基底型を持つ場合や、型エイリアスの場合など、特定の条件下でのみ可能です。 - このコミットの目的は、
SetFinalizer
がこれらのGoの通常の代入規則に従うようにすることです。
-
Goランタイムの内部構造(型表現):
Eface
(Empty Interface): ランタイム内部でinterface{}
型の値を表現するための構造体です。type
(型情報へのポインタ) とdata
(値へのポインタ) を持ちます。Iface
(Interface with Methods): メソッドを持つインターフェース型を表現するための構造体です。tab
(型とメソッド情報を含むテーブルへのポインタ) とdata
(値へのポインタ) を持ちます。Type*
: ランタイムが認識する全ての型に関するメタデータ(型名、サイズ、種類、メソッドなど)を保持する構造体へのポインタです。Kind
フィールドは型の種類(KindFunc
,KindPtr
,KindInterface
など)を示します。FuncVal*
: 関数値の表現です。PtrType*
: ポインタ型に関するメタデータです。elem
フィールドはポインタが指す要素の型を示します。- これらの内部構造は、
SetFinalizer
がファイナライザの引数の型を検証し、実際にファイナライザを実行する際にオブジェクトの値を適切な型に変換するために使用されます。
-
ガベージコレクションとファイナライザの実行:
- Goのガベージコレクタは、到達不能になったオブジェクトを自動的にメモリから解放します。
SetFinalizer
で設定されたファイナライザは、オブジェクトが到達不能になり、GCによって回収される直前のタイミングで実行キューに入れられ、その後、専用のゴルーチンによって実行されます。この実行は非同期であり、タイミングは保証されません。
技術的詳細
このコミットの技術的な核心は、runtime.SetFinalizer
関数がファイナライザの引数として受け入れる型の検証ロジックを、Goの通常の代入規則に準拠するように変更した点にあります。
主な変更点は以下の通りです。
-
SetFinalizer
の引数型チェックの緩和:src/pkg/runtime/malloc.goc
内のSetFinalizer
関数が修正されました。- 以前は、ファイナライザ関数の引数
fint
がobj.type
と完全に一致するか、または空インターフェース (interface{}
) である場合にのみ許可されていました。 - 変更後、
fint
がobj.type
に代入可能であれば、ファイナライザが設定できるようになりました。これは、Goコンパイラがf(x)
のような直接的な関数呼び出しに対して行う型チェックと同じロジックをランタイムに適用することを意味します。 - 具体的には、以下のケースが追加で許容されるようになりました。
fint == obj.type
: 以前から許容。fint
がポインタ型で、obj.type
もポインタ型であり、かつ両者の要素型が同じ場合(例:*int
とTintptr
)。これは、型エイリアスや無名ポインタ型の場合に特に重要です。fint
が空インターフェース (interface{}
) の場合: 以前のCLで追加された緩和。fint
がメソッドを持つインターフェース型であり、obj
がそのインターフェースを満たす場合。このチェックのために、新しいヘルパー関数runtime·ifaceE2I2
が導入されました。
-
runtime·ifaceE2I2
の導入:src/pkg/runtime/iface.c
にruntime·ifaceE2I2
関数が追加されました。- この関数は、
Eface
(空インターフェース) で表現された値を、指定されたメソッドを持つインターフェース型 (InterfaceType *inter
) に変換できるかどうかをチェックし、変換可能であればIface
構造体に変換結果を格納します。変換が成功した場合はtrue
を返します。 SetFinalizer
はこの関数を呼び出して、ファイナライズされるオブジェクトが、ファイナライザ関数の引数として指定された非空インターフェース型を満たすかどうかを検証します。
-
ファイナライザ情報の拡張:
src/pkg/runtime/mfinal.c
に定義されているFin
構造体(ファイナライザの情報を保持)に、Type *fint
フィールドが追加されました。fint
は、ファイナライザ関数の引数として期待される型(つまり、SetFinalizer
に渡されたファイナライザ関数の引数の型)を保持します。runtime·addfinalizer
およびruntime·getfinalizer
関数のシグネチャも変更され、この新しいfint
パラメータを受け渡しするようになりました。これにより、ファイナライザが実際に実行される際に、正しい型情報に基づいてオブジェクトを変換できるようになります。
-
ファイナライザ実行時の型変換ロジックの強化:
src/pkg/runtime/mgc0.c
内のrunfinq
関数(ファイナライザを実行する部分)が修正されました。- ファイナライザを実行する際、
f->fint
(ファイナライザの引数型) の情報に基づいて、ファイナライズされるオブジェクト (f->arg
) を適切な型に変換してファイナライザに渡すロジックが追加されました。 - 具体的には、以下のケースに対応します。
f->fint
がポインタ型の場合: オブジェクトのポインタを直接渡します。f->fint
が空インターフェースの場合: オブジェクトをEface
構造体に変換して渡します。f->fint
がメソッドを持つインターフェース型の場合:runtime·ifaceE2I2
を使用して、オブジェクトをそのインターフェース型に変換し、Iface
構造体として渡します。変換に失敗した場合はランタイムパニックが発生します(これはSetFinalizer
の段階で型チェックが通っているため、通常は発生しないはずです)。
-
エラーメッセージの改善:
SetFinalizer
が不正なファイナライザ関数を受け取った際のエラーメッセージが、より具体的で分かりやすいものに変更されました。- 変更前:
"runtime.SetFinalizer: second argument is %S, not func(%S) or func(interface{})"
- 変更後:
"runtime.SetFinalizer: cannot pass %S to finalizer %S"
- これは、新しい柔軟な型チェックロジックを反映し、Goの通常の型エラーメッセージに近い形式になっています。
-
テストの更新:
src/pkg/runtime/mfinal_test.go
のテストケースが大幅に更新されました。TestFinalizerTypeSucceed
とTestFinalizerInterface
がTestFinalizerType
に統合され、様々な型の組み合わせ(ポインタ型、型エイリアス、空インターフェース、メソッドを持つインターフェース)でSetFinalizer
が正しく動作するかを検証するようになりました。これにより、変更された型チェックロジックが意図通りに機能し、後方互換性も維持されていることが確認されます。
これらの変更により、runtime.SetFinalizer
はGoの型システムにおける代入規則と完全に一致するようになり、より予測可能で使いやすいAPIとなりました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に runtime.SetFinalizer
の型チェックロジックと、ファイナライザ実行時の引数準備ロジックに集中しています。
-
src/pkg/runtime/malloc.goc
のSetFinalizer
関数: ファイナライザ関数の引数fint
とオブジェクトの型obj.type
の間の代入可能性をチェックする部分が拡張されました。--- a/src/pkg/runtime/malloc.goc +++ b/src/pkg/runtime/malloc.goc @@ -763,9 +765,16 @@ func SetFinalizer(obj Eface, finalizer Eface) { \tif(ft->dotdotdot || ft->in.len != 1) \t\tgoto badfunc; \tfint = *(Type**)ft->in.array; -\t\tif(fint->kind == KindInterface && ((InterfaceType*)fint)->mhdr.len == 0) -\t\t\tot = (PtrType*)obj.type; -\t\telse if(fint != obj.type) +\t\tif(fint == obj.type) { +\t\t\t// ok - same type +\t\t} else if(fint->kind == KindPtr && (fint->x == nil || fint->x->name == nil || obj.type->x == nil || obj.type->x->name == nil) && ((PtrType*)fint)->elem == ((PtrType*)obj.type)->elem) { +\t\t\t// ok - not same type, but both pointers, +\t\t\t// one or the other is unnamed, and same element type, so assignable. +\t\t} else if(fint->kind == KindInterface && ((InterfaceType*)fint)->mhdr.len == 0) { +\t\t\t// ok - satisfies empty interface +\t\t} else if(fint->kind == KindInterface && runtime·ifaceE2I2((InterfaceType*)fint, obj, &iface)) { +\t\t\t// ok - satisfies non-empty interface +\t\t} else \t\tgoto badfunc;
-
src/pkg/runtime/iface.c
のruntime·ifaceE2I2
関数: メソッドを持つインターフェースへの変換を試みる新しいヘルパー関数。--- a/src/pkg/runtime/iface.c +++ b/src/pkg/runtime/iface.c @@ -482,6 +482,16 @@ runtime·ifaceE2I(InterfaceType *inter, Eface e, Iface *ret) ret->tab = itab(inter, t, 0); } +bool +runtime·ifaceE2I2(InterfaceType *inter, Eface e, Iface *ret) +{ +\tret->tab = itab(inter, e.type, 1); +\tif(ret->tab == nil) +\t\treturn false; +\tret->data = e.data; +\treturn true; +} + // For reflect // func ifaceE2I(t *InterfaceType, e interface{}, dst *Iface) void
-
src/pkg/runtime/mfinal.c
のFin
構造体とruntime·addfinalizer
: ファイナライザの引数型を保持するためのfint
フィールドが追加され、関連する関数のシグネチャが変更されました。--- a/src/pkg/runtime/mfinal.c +++ b/src/pkg/runtime/mfinal.c @@ -13,7 +14,8 @@ struct Fin { FuncVal *fn; uintptr nret; -\tvoid *ot; +\tType *fint; +\tPtrType *ot; }; // Finalizer hash table. Direct hash, linear scan, at most 3/4 full. @@ -43,7 +45,7 @@ static struct { } fintab[TABSZ]; static void -addfintab(Fintab *t, void *k, FuncVal *fn, uintptr nret, void *ot) +addfintab(Fintab *t, void *k, FuncVal *fn, uintptr nret, Type *fint, PtrType *ot) { \tint32 i, j;\ @@ -68,6 +70,7 @@ ret: \tt->key[i] = k; \tt->val[i].fn = fn; \tt->val[i].i.nret = nret; +\tt->val[i].fint = fint; \tt->val[i].ot = ot; } @@ -126,7 +129,7 @@ resizefintab(Fintab *tab) \tfor(i=0; i<tab->max; i++) { \t\tk = tab->key[i]; \t\tif(k != nil && k != (void*)-1) -\t\t\taddfintab(&newtab, k, tab->val[i].fn, tab->val[i].nret, tab->val[i].ot); +\t\t\taddfintab(&newtab, k, tab->val[i].fn, tab->val[i].nret, tab->val[i].fint, tab->val[i].ot); \t} \t \truntime·free(tab->key); @@ -140,7 +143,7 @@ resizefintab(Fintab *tab) } bool -runtime·addfinalizer(void *p, FuncVal *f, uintptr nret, void *ot) +runtime·addfinalizer(void *p, FuncVal *f, uintptr nret, Type *fint, PtrType *ot) { \tFintab *tab;\ \tbyte *base;\ @@ -169,7 +172,7 @@ runtime·addfinalizer(void *p, FuncVal *f, uintptr nret, void *ot) \t\tresizefintab(tab); \t} -\taddfintab(tab, p, f, nret, ot); +\taddfintab(tab, p, f, nret, fint, ot); \truntime·setblockspecial(p, true); \truntime·unlock(tab); \treturn true; @@ -178,7 +181,7 @@ runtime·addfinalizer(void *p, FuncVal *f, uintptr nret, void *ot) // get finalizer; if del, delete finalizer. // caller is responsible for updating RefHasFinalizer (special) bit. bool -runtime·getfinalizer(void *p, bool del, FuncVal **fn, uintptr *nret, void **ot) +runtime·getfinalizer(void *p, bool del, FuncVal **fn, uintptr *nret, Type **fint, PtrType **ot) { \tFintab *tab;\ \tbool res;\ @@ -192,6 +195,7 @@ runtime·getfinalizer(void *p, bool del, FuncVal **fn, uintptr *nret, void **ot) \t\treturn false; \t*fn = f.fn; \t*nret = f.nret; +\t*fint = f.fint; \t*ot = f.ot; \treturn true; }
-
src/pkg/runtime/mgc0.c
のrunfinq
関数: ファイナライザ実行時に、fint
に基づいてオブジェクトを適切な型に変換して引数として渡すロジックが追加されました。--- a/src/pkg/runtime/mgc0.c +++ b/src/pkg/runtime/mgc0.c @@ -2327,12 +2330,22 @@ runfinq(void) \t\t\t\t\tframe = runtime·mallocgc(framesz, 0, FlagNoPointers|FlagNoInvokeGC); \t\t\t\t\tframecap = framesz; \t\t\t\t} -\t\t\t\tif(f->ot == nil) +\t\t\t\tif(f->fint == nil) +\t\t\t\t\truntime·throw("missing type in runfinq"); +\t\t\t\tif(f->fint->kind == KindPtr) { +\t\t\t\t\t// direct use of pointer \t\t\t\t\t*(void**)frame = f->arg; -\t\t\t\telse { +\t\t\t\t} else if(((InterfaceType*)f->fint)->mhdr.len == 0) { +\t\t\t\t\t// convert to empty interface \t\t\t\t\tef = (Eface*)frame; \t\t\t\t\tef->type = f->ot; \t\t\t\t\tef->data = f->arg; +\t\t\t\t} else { +\t\t\t\t\t// convert to interface with methods, via empty interface. +\t\t\t\t\tef1.type = f->ot; +\t\t\t\t\tef1.data = f->arg; +\t\t\t\t\tif(!runtime·ifaceE2I2((InterfaceType*)f->fint, ef1, (Iface*)frame)) +\t\t\t\t\t\truntime·throw("invalid type conversion in runfinq"); \t\t\t\t} \t\t\t\treflect·call(f->fn, frame, framesz); \t\t\t\tf->fn = nil;
コアとなるコードの解説
SetFinalizer
の型チェックロジック (src/pkg/runtime/malloc.goc
)
変更された SetFinalizer
関数内の型チェックロジックは、ファイナライザ関数の引数 fint
が、ファイナライズされるオブジェクトの型 obj.type
に対してGoの通常の代入規則を満たすかどうかを判断します。
// ... (SetFinalizer 関数の冒頭部分) ...
// finalizer.type はファイナライザ関数の型
// ft は finalizer.type を FuncType* にキャストしたもの
// ft->in.array は引数型の配列
// fint はファイナライザ関数の唯一の引数の型
fint = *(Type**)ft->in.array;
if(fint == obj.type) {
// ok - same type
// ファイナライザの引数型がオブジェクトの型と完全に一致する場合。
// 例: SetFinalizer(v, func(v *int){...})
} else if(fint->kind == KindPtr && (fint->x == nil || fint->x->name == nil || obj.type->x == nil || obj.type->x->name == nil) && ((PtrType*)fint)->elem == ((PtrType*)obj.type)->elem) {
// ok - not same type, but both pointers,
// one or the other is unnamed, and same element type, so assignable.
// ファイナライザの引数型とオブジェクトの型が両方ともポインタ型であり、
// かつ、それらが指す要素の型が同じ場合。
// これは、型エイリアスや無名ポインタ型(例: `type MyIntPtr *int` と `*int`)
// の間で代入が可能なGoのルールを反映しています。
// `fint->x == nil || fint->x->name == nil` は、型が名前を持たない(無名型)
// または型情報が不足している場合に、より柔軟な比較を可能にします。
// 例: type Tintptr *int; SetFinalizer(v, func(v Tintptr){...})
} else if(fint->kind == KindInterface && ((InterfaceType*)fint)->mhdr.len == 0) {
// ok - satisfies empty interface
// ファイナライザの引数型が空インターフェース (interface{}) の場合。
// 以前のCLで追加された緩和です。
// 例: SetFinalizer(v, func(v interface{}){...})
} else if(fint->kind == KindInterface && runtime·ifaceE2I2((InterfaceType*)fint, obj, &iface)) {
// ok - satisfies non-empty interface
// ファイナライザの引数型がメソッドを持つインターフェース型であり、
// かつ、オブジェクトがそのインターフェースを満たす場合。
// `runtime·ifaceE2I2` は、オブジェクト `obj` がインターフェース `fint` に
// 変換可能かどうかをチェックします。
// 例: type Tinter interface{ m() }; type Tint int; func (t *Tint) m() {};
// SetFinalizer((*Tint)(v), func(v Tinter){...})
} else {
// 上記のいずれの条件も満たさない場合、不正なファイナライザとして処理されます。
goto badfunc;
}
// ... (SetFinalizer 関数の残りの部分) ...
このロジックにより、SetFinalizer
はGoのコンパイラが通常行う型チェックと同様の厳密さで、しかしより柔軟にファイナライザの引数型を受け入れるようになりました。
runtime·ifaceE2I2
関数 (src/pkg/runtime/iface.c
)
この関数は、空インターフェース Eface
で表現された値を、メソッドを持つ特定のインターフェース型 InterfaceType *inter
に変換できるかどうかを試みます。
bool
runtime·ifaceE2I2(InterfaceType *inter, Eface e, Iface *ret)
{
// itab(inter, e.type, 1) は、e.type が inter インターフェースを満たすかどうかをチェックし、
// 満たす場合はそのインターフェーステーブル (itab) を返します。
// 第3引数の '1' は、インターフェースを満たさない場合に nil を返すことを意味します。
ret->tab = itab(inter, e.type, 1);
if(ret->tab == nil)
return false; // 変換できない場合
ret->data = e.data; // 変換できる場合、データポインタをコピー
return true; // 変換成功
}
itab
はGoランタイムの内部関数で、特定の具体的な型が特定のインターフェース型を満たすかどうかを判断し、そのためのメソッドテーブル(itab
)を検索または構築します。runtime·ifaceE2I2
はこの itab
を利用して、SetFinalizer
が非空インターフェース型のファイナライザ引数を検証できるようにします。
ファイナライザ実行時の引数準備 (src/pkg/runtime/mgc0.c
の runfinq
)
runfinq
関数は、ガベージコレクションによって回収されるオブジェクトのファイナライザを実際に実行する部分です。ここで、Fin
構造体に保存された fint
(ファイナライザの引数型) を使用して、オブジェクトの値をファイナライザが期待する型に変換します。
// ... (runfinq 関数の冒頭部分) ...
// f は現在処理中のファイナライザ情報を含む Fin 構造体
// f->fint はファイナライザ関数の引数型
// f->arg はファイナライズされるオブジェクトのデータポインタ
if(f->fint == nil)
runtime·throw("missing type in runfinq"); // 型情報がない場合はパニック
if(f->fint->kind == KindPtr) {
// direct use of pointer
// ファイナライザの引数型がポインタ型の場合、オブジェクトのポインタを直接渡します。
*(void**)frame = f->arg;
} else if(((InterfaceType*)f->fint)->mhdr.len == 0) {
// convert to empty interface
// ファイナライザの引数型が空インターフェース (interface{}) の場合、
// オブジェクトを Eface 構造体に変換して渡します。
ef = (Eface*)frame;
ef->type = f->ot; // f->ot はオブジェクトの元の型情報
ef->data = f->arg;
} else {
// convert to interface with methods, via empty interface.
// ファイナライザの引数型がメソッドを持つインターフェース型の場合、
// まずオブジェクトを一時的な空インターフェース (ef1) に変換し、
// その後 runtime·ifaceE2I2 を使って目的のインターフェース型に変換します。
ef1.type = f->ot;
ef1.data = f->arg;
if(!runtime·ifaceE2I2((InterfaceType*)f->fint, ef1, (Iface*)frame))
runtime·throw("invalid type conversion in runfinq"); // 変換失敗はパニック
}
// reflect·call は、準備された引数 (frame) を使ってファイナライザ関数 (f->fn) を呼び出します。
reflect·call(f->fn, frame, framesz);
// ... (runfinq 関数の残りの部分) ...
このロジックにより、SetFinalizer
で設定されたファイナライザは、Goの型システムが許容するあらゆる代入可能な引数型で正しく呼び出されることが保証されます。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/5822e7848a5c355f694c22dce4a1da43f2793441
- Go CL 12895043: https://golang.org/cl/12895043
- Go Issue 5368: https://github.com/golang/go/issues/5368
- Go CL 11858043 (以前の緩和): https://golang.org/cl/11858043
参考にした情報源リンク
- Go言語の公式ドキュメント (runtime.SetFinalizer): https://pkg.go.dev/runtime#SetFinalizer
- Go言語のインターフェースに関する公式ドキュメント: https://go.dev/tour/methods/10
- Go言語の型システムに関する一般的な情報源 (例: Effective Go, Go言語の仕様書)
- Goランタイムのソースコード (特に
src/runtime
ディレクトリ) - GoのIssueトラッカーとCL (Code Review) システム