CA Reward

Tech Blog

Tech Blogトップに戻る

Unsafe in Go 甘美な世界

2016.08.23

  • このエントリーをはてなブックマークに追加
  • Pocket
junchang1031
junchang1031エンジニア

はじめに

こんにちは。開発部の辻です。タイトルは釣りです。

通常のGo言語を使った開発においては、unsafeパッケージを利用する機会はあまりないと思います。 また、名前が示す通り、Go言語が担保してくれている安全性の枠組みを超える部分の機能となりますので、積極的に利用するべきものではありません。 (Google App Engine等、unsafeの利用を禁止しているプラットフォームもあります)

とはいえ、どんなものなのか、何ができるのか、くらいは抑えておきたいと思い、今回のテーマとしました。 あまり実用的な内容ではないことをあらかじめお断りしておきます。

unsafe.Pointerについて

unsafeパッケージをunsafeたらしめているのが、このPointer構造体です。unsafe.Pointerは、あらゆる型の変数のアドレスを保持することができます。C言語のvoid型ポインタ(汎用ポインタ)と同じようなものだという理解でよいと思います。

一見reflectionと大差ないように思えるかもしれませんが、決定的に異なる点があります。それは、

unsafe.Pointerは型情報を持っていない ということです。

つまり、unsafe.Pointerが指し示すアドレスに格納されている値を、利用者側の裁量如何で、 任意の型として取り出すことが出来て、かつ値の更新も出来てしまうということです。

一見すると、非常に便利な性質に思えます。 また、パフォーマンスの面でも、メモリを効率的に使い回すことにより、ケースによっては劇的な改善を実現する事が可能となります。

ただし、裏を返せば非常に危険な性質であるとも言えます。

unsafe.Pointerの危険な性質とは

型安全の恩恵を受けられない

これは型情報を持っていないという以上当たり前なのですが、極めて重大な問題です。 通常の型ポインタであれば、指し示すアドレス上の値のメモリレイアウトがどうなっているのかを完全に保証してくれます。 (intなのかstringなのか[]intなのか構造体なのかetc)

しかし、unsafe.Pointerを使う場合はそのメモリのレイアウトを開発者がきちんと把握・管理をしなければなりません。 仮にミスがあった場合、全くもって意味のない値が取れたり、最悪の場合メモリを破壊することも起こりえます。

利用者が、C言語などメモリ管理を自前で行う言語に精通していれば、あるいは問題ないのかもしれません。 しかし、仮にそういったスキルを持っているエンジニアにとってみても、Go言語で同じことをやることには苦痛を覚えるのではないでしょうか。 (だったらC言語を使おうとなりませんか?)

Goバージョン互換の保証が無くなる

unsafeパッケージを使った処理では、比較的低レベルな処理を行うことが多く、結果、Go言語の実装詳細(かつ明示的な仕様として公開されていない)に依存してしまうことが発生しがちです。例えば、構造体のフィールドが宣言された順番でメモリにレイアウトされることは、言語仕様的には保証されていませんが、現状のバージョンでは特に並べ替えはしていません。この動きに依存して処理を書いてしまっていると、仮に将来のバージョンでこの動きが変わった場合に問題が発生してしまうことになります。

ポインタ演算ができてしまう

Go言語にもSizeofAlignof Offsetofといったポインタ演算用途な関数は用意されており、 unsafe.Pointerをuintptr型に変換し、これらの関数と組み合わせることでポインタの演算が可能になります。

ポインタ演算が危険な理由は型安全のくだりとほぼ同じですが、 それよりも何よりも、皆さんポインタ演算やりたいですか?私はやりたくないです...

unsafe.Pointerによる型変換と値更新サンプル

さて、危険な性質を理解していただいたところで、型変換と値更新のサンプルコードです。 コードと出力結果を見れば大体わかると思いますので説明は割愛します。

終わりに

いかがだったでしょうか。これを読んでunsafeパッケージを使うのはやめておこうと思った貴方。 それは極めて正常な思考だと思います(笑)

ただし、なんでもかんでもダメ、という訳ではなく、例えばプロダクトコードとは別のテストの領域や、 局所化・限定化された処理においてパフォーマンスを向上させる、みたいな使い方であれば、 (本当に最後の手段としての)選択肢として考えてみるのは、ありかもしれないと思いました。

ちなみに、標準ライブラリにおいて低レベルな処理を担当している、例えば以下のようなパッケージでunsafeを使っています。

  • reflection
  • runtime
  • os
  • syscall
  • net

詳しく学びたい方はこちらのパッケージのソースコードを読んでみると良いかもしれません。

今回は以上です。
junchang1031
junchang1031エンジニア