0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Goのインターフェースでnilがnilでない時

Posted at

表紙

Go 言語でコーディングをしていると、ある特定の状況に遭遇することがあります。つまり、インターフェース型(たとえば anyinterface{})の引数で他の値を受け取るとき、与えられた引数が明らかに nil であるにもかかわらず、x == nil という不等式が成立しない場合があります。これはなぜでしょうか?本記事ではその謎を解き明かします。

ケーススタディ

package main

import "fmt"

func main() {
  var a any = nil
  var b *int = nil
  fmt.Println("isNil: ", isNil(a))
  fmt.Println("isNil: ", isNil(b))
  fmt.Println("b == nil: ", b == nil)
}

func isNil(x any) bool {
  return x == nil
}

プログラムの実行結果:

isNil:  true
isNil:  false
b == nil:  true

上記の Go コードの例は、any(すなわち interface{})型を使用したときに、変数が nil かどうかを判断する際の微妙な違いを示しています。特に、ある状況下では、値が nil であるにもかかわらず、any 型の変数は nil として扱われない理由を示しています。

このコード例では、まず isNil 関数を定義し、その引数に x any を使用しています。そして main 関数では、2 つの異なる型の変数 ab を初期化し、いずれも nil を代入しています。次に、それぞれの変数を引数として isNil 関数を呼び出しています。また、b == nil の値を直接出力し、比較しています。

実行結果を見ると、isNil 関数内では a == niltrue ですが、b == nilfalse となっています。一方、外部では b == niltrue となっています。これは、isNil 関数内で any 型の変数を使っているため、x == nil が成立しないという現象です。この仕組みを理解するためには、any の内部構造について知る必要があります。詳細は以下をご覧ください。

any(interface{}) の内部構造

Go 言語において、anyinterface{} の別名です。interface{} 型の値は、内部的には 型情報値情報 の 2 つの部分から構成されています。

  • 型情報(Type Part):インターフェースが保持する具体的な型です。たとえば、インターフェースが *int 型の値を保持している場合、この型情報は *int になります。
  • 値情報(Value Part):インターフェースが保持する具体的な値です。値が整数 3 であればそれが保存され、nil であれば nil が保存されます。

値をインターフェース型(例:any)に代入する際、インターフェースはその値の「型」と「具体的な値」の両方を内部に保持します。そして、インターフェースが nil とみなされるのは、「型情報」と「値情報」の両方が nil である場合のみです。

先ほどのコード例を思い出してください。変数 b(型は *int、値は nil)をインターフェース型の変数 x に代入した時、x の内部構造は type = *intvalue = nil という状態になります。このとき、型情報が nil ではないため、x == nil成立しません

リフレクションを使って nil をチェックする

==!= といった比較だけでは、インターフェース型の変数が本当に nil かどうかを完全には判断できないことが分かりました。それでは、どうすれば正確に判定できるのでしょうか?
その答えは、リフレクション(反射)機構を使うことです。

リフレクションを使えば、変数の値が nil であるかどうかを正確に調べることができます。

func isNil(x any) bool {
  if x == nil {
    return true
  }
  value := reflect.ValueOf(x)
  switch value.Kind() {
  case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
    return value.IsNil()
  default:
    return false
  }
}

このように、reflect.ValueOf を使って変数の具体的な型や値を取得し、さらに Kind() を使ってそのカテゴリを判定したうえで IsNil() を呼び出せば、より正確な nil 判定が可能になります。

まとめ

本記事では、Go 言語において、インターフェース型の変数が nil の値を持っているにもかかわらず、nil と等しくないと判定される理由について深く掘り下げました。具体的なコード例と、any(つまり interface{})の内部構造の解説を通じて、この現象の本質を明らかにしました。

重要なポイントは以下のとおりです:

  • インターフェース型の内部構造any(すなわち interface{})の内部は、「型情報」と「値情報」の 2 つから構成されている。この 2 つが同時に nil のときに限り、そのインターフェースは nil とみなされる
  • 解決策reflect パッケージを使うことで、インターフェース型の変数が本当に nil かどうかを正確に判断することができる。

このような Go 言語特有の仕様を理解しておくことで、予期せぬバグや混乱を避けることができ、より堅牢で正確なプログラムを書くことが可能になります。


私たちはLeapcell、Goプロジェクトのホスティングの最適解です。

Leapcell

Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです:

複数言語サポート

  • Node.js、Python、Go、Rustで開発できます。

無制限のプロジェクトデプロイ

  • 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

比類のないコスト効率

  • 使用量に応じた支払い、アイドル時間は課金されません。
  • 例: $25で6.94Mリクエスト、平均応答時間60ms。

洗練された開発者体験

  • 直感的なUIで簡単に設定できます。
  • 完全自動化されたCI/CDパイプラインとGitOps統合。
  • 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

簡単なスケーラビリティと高パフォーマンス

  • 高い同時実行性を容易に処理するためのオートスケーリング。
  • ゼロ運用オーバーヘッド — 構築に集中できます。

ドキュメントで詳細を確認!

Try Leapcell

Xでフォローする:@LeapcellHQ


ブログでこの記事を読む

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?