Goプロポーザル deep dive 候補: errors と型チェッカー¶
結論¶
説明ネタとしての扱いやすさは、以下の順。
| 候補 | 主資料 | 難易度 | 向いている説明 |
|---|---|---|---|
errors.AsType |
#51945 | 中 | Go proposal の読み方、標準ライブラリ API 設計、ジェネリクス導入後の ergonomic 改善 |
errors.Join / error tree |
#53435 | 中〜難 | error wrapping の設計、互換性、標準ライブラリに入れる理由 |
errors.Iter / IterAs |
#66455 | 難 | errors.Is / As で足りないケース、error tree の全探索 API 設計 |
| 型構築と cycle detection | Go Blog, #75883 | 難 | Go コンパイラ内部、型チェッカー、不完全型、循環参照検出 |
まず説明するなら errors.AsType がよい。
理由は、現場での errors.As の書きにくさから入れるので読者が追いやすく、proposal issue の問題設定も短い。
一方で「難易度の話」をちゃんとしたいなら、型チェッカー記事はかなり良い。 ただし proposal issue 単体ではなく、Go 1.26 の内部改善ブログと関連 bug issue を読む形になる。
候補1: errors.AsType¶
主 issue: errors: AsType (As with type parameters) #51945
Go 1.26 release notes: errors.AsType
何の話か¶
従来の errors.As は、対象のエラー型を先に変数として宣言し、そのアドレスを渡す必要がある。
Go 1.26 の errors.AsType[E error] は、これをジェネリクスで書けるようにした API。
何がうれしいか¶
- 変数のスコープを
ifブロックに閉じ込められる &targetのような pointer-to-pointer っぽい見た目を避けられるtarget anyを受けるerrors.Asより型安全にしやすい- release notes では、型安全・高速・多くの場合に使いやすい、と説明されている
注意点¶
errors.As は「error を実装する型」だけでなく、任意の interface 型にもマッチできる。
一方、errors.AsType[E error] は型パラメータ E が error を満たす必要がある。
つまり、単なる interface{ A() } を探すなら errors.As が必要。
AsType でやるなら interface { error; A() } のように error も含める必要がある。
難易度¶
中。
アプリケーションエンジニア向けには「errors.As の面倒さを generic API で改善した」で説明できる。
ただし深掘りするなら、以下を押さえる必要がある。
- error wrapping は単なる型アサーションではなく、error tree を探索する
errors.Asは customAs(any) boolメソッドも尊重するAsTypeはAsの置き換えではなく、よくある形を安全・簡潔にする API- 標準ライブラリ API は名前、互換性、既存 API との重複をかなり慎重に見る
候補2: errors.Join と error tree¶
主 issue: errors: add support for wrapping multiple errors #53435
Go 1.20 release notes: errors.Join
何の話か¶
Go 1.13 で Unwrap() error と errors.Is / errors.As が入り、error wrapping が標準化された。
その後、複数の error を1つの error として扱いたい需要が残った。
Go 1.20 では以下が入った。
errors.Join(errs ...error)fmt.Errorfで複数の%wUnwrap() []errorerrors.Is/errors.Asが error chain ではなく error tree を探索する考え方
難しいところ¶
「複数 error をまとめるだけ」なら簡単に見える。 しかし標準ライブラリに入れるには、次の設計判断が必要になる。
Unwrap() errorとUnwrap() []errorをどう共存させるかerrors.Isは複数の子のどれかに一致すれば true でよいかerrors.Asは複数マッチする場合にどれを返すかerrors.Joinはネストした join error を flatten すべきか- 個別 error を取り出す公式 API をどこまで用意すべきか
この周辺で、あとから以下の proposal も出ている。
難易度¶
中〜難。
errors.Join 自体の使い方は簡単。
しかし proposal を読む題材としては、標準ライブラリが「便利関数」を入れるだけでは済まないことを説明できる。
特に As が「最初に見つかった型」を返す以上、複数 error の tree で「全部見たい」需要が残る、という話が深い。
候補3: 型構築と cycle detection¶
X 元ネタ: turbofish の投稿
リンク先: Type Construction and Cycle Detection
関連 proposal: spec: remove cycle restriction for type parameters #75883
関連 bug:
- #75918: invalid cyclic program involving
unsafe.Sizeofで panic - #76383:
unsafe.Sizeofの結果が不正 - #76384:
unsafe.Sizeofで type checker panic - #76478: value type の式で
unsafe.Sizeofが panic
何の話か¶
Go の型チェッカーは、AST を見ながら内部の型表現を作る。 これが type construction。
単純な例なら簡単。
T の underlying type を作るには []U が必要で、U を作るには *int が必要。
依存関係をたどって、下から完成させていけばよい。
問題は再帰型。
この場合、T を作っている途中でまた T に戻る。
ただし *T のようにポインタ越しなら、T の中身を今すぐ覗かなくてもよい。
未完成の T を指しておき、あとで全体が完成すれば成立する。
どこから難しくなるか¶
配列型の長さは型の一部で、コンパイル時定数でなければならない。
その長さ計算で unsafe.Sizeof が絡むと、型の中身を覗く必要が出る。
この例では、T の長さを知るために T{} のサイズが必要。
しかし T{} のサイズを知るには、T の完成が必要。
つまり依存関係がこうなる。
これは正当な再帰型ではなく、解けない循環。 型チェッカーは panic するのではなく、cycle error として報告しなければならない。
Go 1.26 の改善の要点¶
ブログの要点は、未完成な型を持つ値が「下流」に流れてから壊れるのを待つのではなく、未完成値が生まれる「上流」で止める、という整理。
例:
- conversion:
T(42) - function call:
f() - type assertion:
i.(T) - channel receive:
<-ch - map access:
m[k] - dereference:
*new(T)
これらが未完成な型の値を作るなら、その時点で検出する。 結果として、型構築アルゴリズムを単純化しつつ、cycle detection の精度を上げられる。
proposal として見るなら¶
X のリンク先ブログそのものは proposal issue ではない。 proposal issue として扱うなら #75883 が近い。
Go 1.26 では、generic type が型パラメータリスト内で自分自身を参照する制限が外れた。
この proposal は「型チェッカーがもう扱えるなら、不要な仕様制限を消そう」という話。 背景には #68162 のような、妥当に見える generic interface が invalid recursive type として拒否されていた問題がある。
難易度¶
難。
難しさの中心は、Go の表面文法ではなく、型チェッカー内部の不変条件にある。
- defined type と underlying type の違い
- 型が「完成している」とは何か
- 再帰型でも許される循環と、許されない循環の違い
unsafe.Sizeofや配列長のように、型構築中に型を deconstruct する処理- 「未完成型への参照」は許せても、「未完成型を覗く」は許せないという線引き
この題材は、Go を使う人向けの「新機能紹介」よりも、コンパイラや型システムの読み物として深い。
説明するなら、まず type T []U; type U *T のような許される再帰型を出し、そのあと type T [unsafe.Sizeof(T{})]int で壊れる循環を出すと理解しやすい。
使い分け¶
短めにまとめるなら errors.AsType。
proposal issue を読む練習としてちょうどよく、Go 1.26 の新標準 API としても実用に近い。
深掘り回にするなら errors.Join から error tree、さらに errors.Iter の未採択 proposal へ進むとよい。
「なぜ便利そうなのにすぐ入らないのか」を説明できる。
難易度の話を主役にするなら、型構築と cycle detection。 これは proposal の採否よりも、「簡単に見える言語仕様の裏で、コンパイラがどんな状態管理をしているか」を理解する題材として強い。