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

[インデックス 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 の挙動には、以下のような経緯と問題点がありました。

  1. 初期の厳格な型要件: SetFinalizer(x, f) を呼び出す際、ファイナライザ関数 f の唯一の引数は、ファイナライズされるオブジェクト x の型と「完全に一致」する必要がありました。例えば、*int 型のオブジェクトに対してファイナライザを設定する場合、ファイナライザ関数も func(*int) のシグネチャを持つ必要がありました。これは非常に厳格であり、Goの柔軟な型システム(特にインターフェースや型変換)とは整合性が低いものでした。

  2. CL 11858043 による非標準的な緩和: 以前のコミット(CL 11858043)では、この要件が緩和され、ファイナライザ関数の引数が x の型、または interface{} であれば許容されるようになりました。これは一見すると柔軟性をもたらすように見えますが、interface{} 以外の任意のインターフェース型や、x の型に代入可能な他の具体的な型(例えば、*int を受け取るファイナライザに Tintptr 型のポインタを渡す場合など)を許容しないという点で、Goの通常の型システムにおける代入規則とは異なる、特殊なルールとなっていました。

  3. 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言語の概念とランタイムの内部構造に関する知識が役立ちます。

  1. runtime.SetFinalizer:

    • Goの runtime パッケージが提供する関数で、オブジェクトがガベージコレクタによってメモリから解放される直前に実行される関数(ファイナライザ)を設定します。
    • シグネチャは func SetFinalizer(obj interface{}, finalizer interface{}) です。
    • obj はポインタ型である必要があります(例: new(int) や構造体リテラルのアドレス)。
    • finalizerobj を引数として受け取る関数である必要があります。
    • ファイナライザは、リソースのクリーンアップ(ファイルハンドルのクローズ、ネットワーク接続の切断など)に利用されることがありますが、その実行タイミングはGCに依存するため、確実なリソース管理には defer や明示的なクローズ関数が推奨されます。
  2. Goのインターフェース:

    • Goのインターフェースは、メソッドのシグネチャの集合を定義する型です。
    • 空インターフェース (interface{}): 任意の型の値を保持できる特別なインターフェースです。Goの全ての型は interface{} を実装します。
    • 非空インターフェース: 1つ以上のメソッドシグネチャを持つインターフェースです。特定のメソッドを実装する型のみがそのインターフェースを満たします。
    • 型アサーションと型変換: Goでは、インターフェース値が保持する具体的な型を value.(Type) の形式でアサートしたり、異なる型間で変換したりすることができます。このコミットでは、ランタイム内部でオブジェクトの型をファイナライザの引数型に変換するロジックが重要になります。
  3. Goの型システムにおける代入規則:

    • Goでは、ある型の値が別の型の変数に代入できるかどうかは、厳密な規則に基づいています。
    • 具体的な型からインターフェース型への代入: 具体的な型がインターフェースの全てのメソッドを実装していれば、そのインターフェース型に代入可能です。
    • ポインタ型: *T 型は T 型のポインタを表します。*T から interface{} への代入は可能ですが、*T から *U への代入は、TU が同じ基底型を持つ場合や、型エイリアスの場合など、特定の条件下でのみ可能です。
    • このコミットの目的は、SetFinalizer がこれらのGoの通常の代入規則に従うようにすることです。
  4. Goランタイムの内部構造(型表現):

    • Eface (Empty Interface): ランタイム内部で interface{} 型の値を表現するための構造体です。type (型情報へのポインタ) と data (値へのポインタ) を持ちます。
    • Iface (Interface with Methods): メソッドを持つインターフェース型を表現するための構造体です。tab (型とメソッド情報を含むテーブルへのポインタ) と data (値へのポインタ) を持ちます。
    • Type*: ランタイムが認識する全ての型に関するメタデータ(型名、サイズ、種類、メソッドなど)を保持する構造体へのポインタです。Kind フィールドは型の種類(KindFunc, KindPtr, KindInterface など)を示します。
    • FuncVal*: 関数値の表現です。
    • PtrType*: ポインタ型に関するメタデータです。elem フィールドはポインタが指す要素の型を示します。
    • これらの内部構造は、SetFinalizer がファイナライザの引数の型を検証し、実際にファイナライザを実行する際にオブジェクトの値を適切な型に変換するために使用されます。
  5. ガベージコレクションとファイナライザの実行:

    • Goのガベージコレクタは、到達不能になったオブジェクトを自動的にメモリから解放します。
    • SetFinalizer で設定されたファイナライザは、オブジェクトが到達不能になり、GCによって回収される直前のタイミングで実行キューに入れられ、その後、専用のゴルーチンによって実行されます。この実行は非同期であり、タイミングは保証されません。

技術的詳細

このコミットの技術的な核心は、runtime.SetFinalizer 関数がファイナライザの引数として受け入れる型の検証ロジックを、Goの通常の代入規則に準拠するように変更した点にあります。

主な変更点は以下の通りです。

  1. SetFinalizer の引数型チェックの緩和:

    • src/pkg/runtime/malloc.goc 内の SetFinalizer 関数が修正されました。
    • 以前は、ファイナライザ関数の引数 fintobj.type と完全に一致するか、または空インターフェース (interface{}) である場合にのみ許可されていました。
    • 変更後、fintobj.type に代入可能であれば、ファイナライザが設定できるようになりました。これは、Goコンパイラが f(x) のような直接的な関数呼び出しに対して行う型チェックと同じロジックをランタイムに適用することを意味します。
    • 具体的には、以下のケースが追加で許容されるようになりました。
      • fint == obj.type: 以前から許容。
      • fint がポインタ型で、obj.type もポインタ型であり、かつ両者の要素型が同じ場合(例: *intTintptr)。これは、型エイリアスや無名ポインタ型の場合に特に重要です。
      • fint が空インターフェース (interface{}) の場合: 以前のCLで追加された緩和。
      • fint がメソッドを持つインターフェース型であり、obj がそのインターフェースを満たす場合。このチェックのために、新しいヘルパー関数 runtime·ifaceE2I2 が導入されました。
  2. runtime·ifaceE2I2 の導入:

    • src/pkg/runtime/iface.cruntime·ifaceE2I2 関数が追加されました。
    • この関数は、Eface (空インターフェース) で表現された値を、指定されたメソッドを持つインターフェース型 (InterfaceType *inter) に変換できるかどうかをチェックし、変換可能であれば Iface 構造体に変換結果を格納します。変換が成功した場合は true を返します。
    • SetFinalizer はこの関数を呼び出して、ファイナライズされるオブジェクトが、ファイナライザ関数の引数として指定された非空インターフェース型を満たすかどうかを検証します。
  3. ファイナライザ情報の拡張:

    • src/pkg/runtime/mfinal.c に定義されている Fin 構造体(ファイナライザの情報を保持)に、Type *fint フィールドが追加されました。
    • fint は、ファイナライザ関数の引数として期待される型(つまり、SetFinalizer に渡されたファイナライザ関数の引数の型)を保持します。
    • runtime·addfinalizer および runtime·getfinalizer 関数のシグネチャも変更され、この新しい fint パラメータを受け渡しするようになりました。これにより、ファイナライザが実際に実行される際に、正しい型情報に基づいてオブジェクトを変換できるようになります。
  4. ファイナライザ実行時の型変換ロジックの強化:

    • src/pkg/runtime/mgc0.c 内の runfinq 関数(ファイナライザを実行する部分)が修正されました。
    • ファイナライザを実行する際、f->fint (ファイナライザの引数型) の情報に基づいて、ファイナライズされるオブジェクト (f->arg) を適切な型に変換してファイナライザに渡すロジックが追加されました。
    • 具体的には、以下のケースに対応します。
      • f->fint がポインタ型の場合: オブジェクトのポインタを直接渡します。
      • f->fint が空インターフェースの場合: オブジェクトを Eface 構造体に変換して渡します。
      • f->fint がメソッドを持つインターフェース型の場合: runtime·ifaceE2I2 を使用して、オブジェクトをそのインターフェース型に変換し、Iface 構造体として渡します。変換に失敗した場合はランタイムパニックが発生します(これは SetFinalizer の段階で型チェックが通っているため、通常は発生しないはずです)。
  5. エラーメッセージの改善:

    • SetFinalizer が不正なファイナライザ関数を受け取った際のエラーメッセージが、より具体的で分かりやすいものに変更されました。
    • 変更前: "runtime.SetFinalizer: second argument is %S, not func(%S) or func(interface{})"
    • 変更後: "runtime.SetFinalizer: cannot pass %S to finalizer %S"
    • これは、新しい柔軟な型チェックロジックを反映し、Goの通常の型エラーメッセージに近い形式になっています。
  6. テストの更新:

    • src/pkg/runtime/mfinal_test.go のテストケースが大幅に更新されました。
    • TestFinalizerTypeSucceedTestFinalizerInterfaceTestFinalizerType に統合され、様々な型の組み合わせ(ポインタ型、型エイリアス、空インターフェース、メソッドを持つインターフェース)で SetFinalizer が正しく動作するかを検証するようになりました。これにより、変更された型チェックロジックが意図通りに機能し、後方互換性も維持されていることが確認されます。

これらの変更により、runtime.SetFinalizer はGoの型システムにおける代入規則と完全に一致するようになり、より予測可能で使いやすいAPIとなりました。

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

このコミットにおけるコアとなるコードの変更は、主に runtime.SetFinalizer の型チェックロジックと、ファイナライザ実行時の引数準備ロジックに集中しています。

  1. src/pkg/runtime/malloc.gocSetFinalizer 関数: ファイナライザ関数の引数 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;
    
  2. src/pkg/runtime/iface.cruntime·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
    
  3. src/pkg/runtime/mfinal.cFin 構造体と 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;
      }
    
  4. src/pkg/runtime/mgc0.crunfinq 関数: ファイナライザ実行時に、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.crunfinq)

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の型システムが許容するあらゆる代入可能な引数型で正しく呼び出されることが保証されます。

関連リンク

参考にした情報源リンク

  • 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) システム