Menu

Go言語の型アサーション:x.(T)、カンマok、型スイッチ

型アサーションは、インターフェースから具体的な値を取り出します。x.(T)、決してpanicしないカンマok形式、別のインターフェースへのアサーション、型スイッチ、そしてラップされたエラーのためのerrors.Asを解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

x.(T):インターフェースから値を取り出す

インターフェースの値は、保持している具体的な型を隠しています。型アサーション x.(T) はそれを取り戻すよう求めます。

出力:

gopher 6
0 false
string of length 6

x はインターフェース型でなければなりません。具体的な型に対してアサーションするとコンパイルに失敗します:invalid operation: s (variable of type string) is not an interface

1値の形はpanicする

アサーションが間違っていると、1値の形は両方の型を示すメッセージとともにpanicします。

出力:

recovered: interface conversion: interface {} is int, not string

1値の形を使うのは、別の型が来たらクラッシュさせたいプログラミングのエラーであるときだけにします。それ以外はすべてカンマokを使います。失敗したとき、カンマokは T のゼロ値と false を返します。

nilのインターフェースに対するアサーションも失敗します。var v any; v.(string)interface conversion: interface {} is nil, not string でpanicし、カンマokの形は "", false を返します。

別のインターフェースへのアサーション

T はインターフェース型でも構いません。その場合、動的な値が T を実装していればアサーションは成功し、結果は同じ動的な値を保持します。標準ライブラリはこの方法で、省略可能な機能があるかをチェックしています。

io.Copy は、コピー元が io.WriterTo を実装しているかをチェックし、実装していればそれを使います。fmtStringererror をチェックします。このパターンによって、小さなインターフェースを小さいまま保ちつつ、呼び出し側はより豊かな実装の恩恵を受けられます。

型スイッチ

値がいくつかの型のどれかでありうるとき、型スイッチはアサーションの連鎖の代わりになります。x.(type)switch の中でしか使えません。

出力:

hi
integer 7
integer 8
3.14
nil
error: boom
unhandled []int

知っておきたいルールは次のとおりです。

  • 型が1つのcaseでは、x はその型になります。複数の型を並べたcaseと default では、x は元のインターフェースの型(ここでは any)です。
  • caseは順にチェックされます。値は複数のインターフェースを満たしうるので、より具体的なインターフェースを広いものより前に置きます。
  • case nil に一致するのはnilのインターフェースだけで、nilポインタを保持するインターフェースは一致しません。
  • 型スイッチに fallthrough はありません。

errors.As:ラップされたエラーのためのアサーション

エラーは fmt.Errorf("load config: %w", err) のように文脈付きでラップされることがよくあります。直接の型アサーションは最も外側のエラーしか見ないので、中のエラーを見逃します。errors.As は連鎖をたどります。

出力:

type assertion finds it: false
errors.As finds it: open /no/such/file

エラーに型アサーションを使うのは、エラーがラップされていないとわかっているときだけで、実際にはほぼ皆無です。errors.Aserrors.Is、独自のエラー型の定義はカスタムエラーのページで扱っています。

アサーション、変換、ジェネリクスの使い分け

手元にあるものほしいもの使うもの
intfloat64変換:float64(n)
int を保持する anyその intアサーション:v.(int)
いくつかの型のどれかである any型で分岐する型スイッチ
ラップされているかもしれない error特定のエラー型errors.As
多くの型で動く関数コンパイル時の安全性ジェネリクス

any と型スイッチだらけのコードは、ジェネリクスや適切なインターフェースのほうが意図をよく表し、間違いをコンパイル時に捕まえられるというサインであることがよくあります。

よくある間違い

  • 信頼できないデータにpanicする形を使う。 デコードしたJSON、any 型のマップの値、プラグインからの入力には、常にカンマokを使います。
  • 間違った数値型にアサーションする。 JSONの数値は anyfloat64 としてデコードされるので、v.(int) は失敗します。
  • ポインタが格納されているのに値の型にアサーションする。 インターフェースが *User を保持していれば、v.(User) は失敗します。v.(*User) でアサーションします。
  • エラーに型アサーションを使う。 errors.As を使います。

よくある質問

Goの型アサーションとは何ですか?

x がインターフェースの値であるときの式 x.(T) です。T が具体的な型なら、x に格納された値を T として返します。T がインターフェース型なら、格納された値が T も実装しているかをチェックします。1値の形はチェックに失敗するとpanicし、2値の形 v, ok := x.(T) は結果を ok で報告します。

Goでインターフェースの値の型を確認するには?

型スイッチを使います:switch v := x.(type) { case int: ...; case string: ...; default: ... }。各caseの中で v はそのcaseの型になります。1つの型だけなら、カンマokのアサーション s, ok := x.(string) のほうが短く書けます。デバッグでは fmt.Printf("%T", x) で型名を表示できます。

Goの型アサーションと型変換の違いは何ですか?

変換 T(x) は、float64(n) のように、ある型の値を別の型に変えるものです。許されるかどうかはコンパイラが判断し、実行時に失敗することはありません。アサーション x.(T) はインターフェースの値にだけ使え、中にどの型が格納されているかを実行時にチェックします。intを保持する any に対して int(x) とは書けず、x.(int) が必要です。

エラーの型を調べるのに型アサーションを使うべきですか?

いいえ。errors.As(err, &target) を使います。直接のアサーション err.(*MyError) は外側のエラーしか見ないので、エラーが fmt.Errorf("...: %w", err) でラップされた途端に失敗します。errors.As はラップの連鎖をたどります。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める