2026年2月15日
go 1.26.0 ではerrors.AsTypeがリリースされた。
これは、errors.Asに対してジェネリクスを活用して型パラメータを指定できるというもの。わかりやすいメリットとして、事前変数の定義が不要になることでコードがスッキリすることが挙げられる。
// Before
var pathErr *os.PathError
if errors.As(err, &pathErr) {
...
}
// After
if pathErr := errors.AsType[*os.PathError](err); pathErr != nil {
...
}
他の解説記事では、さらなるメリットとして「より型安全になる」と説明されていた。これだけではよくわからなかったので調べてみた。
結論としては、引数としてのエラー型を間違って渡した際に、
errors.As→ 実行時エラーerrors.AsType→ コンパイルエラーになるという違いだった。
errors.Asは型をanyとして受け取った後、リフレクションで実行時に型を検証しており、
不正な型の場合はpanicする。これはコンパイル時には検出できない。
102func As(err error, target any) bool {
103 if err == nil {
104 return false
105 }
106 if target == nil {
107 panic("errors: target cannot be nil")
108 }
109 val := reflectlite.ValueOf(target)
110 typ := val.Type()
111 if typ.Kind() != reflectlite.Ptr || val.IsNil() {
112 panic("errors: target must be a non-nil pointer")
113 }
114 targetType := typ.Elem()
115 if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
116 panic("errors: *target must be interface or implement error")
117 }
118 return as(err, target, val, targetType)
119}一方でerrors.AsTypeはジェネリクスを導入しているために、コンパイル時に型チェックが行われる。
167func AsType[E error](err error) (E, bool) {
168 if err == nil {
169 var zero E
170 return zero, false
171 }
172 var pe *E // lazily initialized
173 return asType(err, &pe)
174}以下のコードのように間違った型を渡してしまった際、errors.Asでは実行時までわからなかったものが、errors.AsTypeではコンパイル時に指摘してくれる。この違いにより「型安全になった」と言える。
package main
import (
"errors"
"os"
)
func main() {
err := &os.PathError{Op: "open", Path: "/tmp/test"}
// errors.As: 実行時パニック
var wrongType string
errors.As(err, &wrongType) // コンパイルOK、実行時パニック
// errors.AsType: (コメントアウトを外すと)コンパイルエラー
// wrongType, ok := errors.AsType[string](err)
// _ = ok
}
https://go.dev/play/p/Ke5NNVqpuO-
さらに、reflectionを利用しなくなったことでパフォーマンス向上が見込めるということで、今後はerrors.AsTypeに乗り換えていくのが良さそう。