TIGの伊藤真彦です。
この記事はGo 1.16連載の1記事目です。
トップバッターとしてgo:embedについて記事を書きます。
go:embedとは
プロポーザルとなるissueはこちら、2020年9月のissue作成から約5ヶ月の時を経てgo:embedがリリースに含まれることになりました。
embedとは埋め込みという意味です、その名の通りファイル埋め込みをサポートするためのパッケージです。ファイルを読み込むだけならosやio/ioutilでも行うことが可能ですが、go:embedならではの特徴を説明します。
ちなみにio/ioutilはGo 1.16でdeprecatedになりました、詳しくは連載の他の記事で説明します。
利用方法
手始めにサンプルコード、main.goを書いてみました。
package main |
他のパッケージと同様importして利用できます。
単一のファイルを埋め込みするだけなら_ "embed"として先頭に_をつけてインポートすることが推奨されています。
このmain.goと同一のディレクトリにsample.jsonを配置します。
{ |
このコードを実行するとsampleBytesにsample.jsonの中身が反映され、構造体sampleの中身が出力されます
go run .\main.go |
何が起きているのか
go:embedでは一見コメントアウトに見える//go:embed sample.jsonが埋め込みファイルの場所を指示する記述として機能します。
//go:embed sample.json |
コメントアウト部分のファイル名を書き換えると参照するファイル名が変わることが確認できます。
//go:embed sampl.json |
go run .\main.go |
同じことをosで実現しようとするとファイル読み込み~変数の格納までそれなりな行数を要するので、わずか2行でファイルを変数に格納できるのは便利ですね。
勿論json以外でも埋め込み可能です、txt形式の文章や画像なども、バイナリファイルとして扱う事が可能です。
うっかり//の後に半角スペースを入れてしまうと本当にコメントアウトとして処理されてしまうのでご注意ください。
コメントアウトと埋め込みの違いがシンタックスハイライトとして反映できるようになると嬉しいかもしれませんね。
// go:embed sampl.json |
その他具体的な利用方法
複数ファイルを埋め込む
go:embedは複数のファイルをまとめて埋め込む使い方が用意されています。embedパッケージを_無しでインポートして、embed.FS型のファイルシステムとして変数に埋め込みます。
この使い方で作成した変数staticはio/fsパッケージでも取り扱う事ができました。
package main |
実行結果は下記のようになります。
go run .\main.go |
net/httpパッケージで提供されているファイルシステムとも互換性があるため。
Web APIを開発する場合は大きなメリットとなる事が期待されています。
以下の15行程度の処理で簡易Webサーバーを立てることができました。
package main |
適当なindex.htmlを用意し、ブラウザでlocalhost:8080/index.htmlにアクセスすることでHTMLを表示できました。
別ディレクトリのファイルを参照する
ファイルがmain.go等実行ファイルと同じ場所にない場合は、パス名を指定することが可能です。embedという名称のアセット管理用フォルダを作成し、sample.jsonを格納した場合は下記のように記載します。
//go:embed embed/sample.json |
先頭に./等のカレントディレクトリを表す表記は不要です。
//go:embed ./embed/sample.json |
また、親ファイルまで遡って読み込みを行うような機能は現在サポートされていませんでした。
//go:embed ../embed/sample.json |
いずれもinvalid pattern syntaxとして処理されます。
go run .\main.go |
複数のファイルをまとめて埋め込む
go:embedではワイルドカードが利用できるため、階層を掘り下げる形であれば複数ファイルをまとめて配置するようなことも可能です。
//go:embed static/* |
先ほどの簡易Webサーバーを、ワイルドカードを利用して、favicon.icoとindex.htmlをフォルダごと読み込み、展開するような構成に変更してみました。
ディレクトリ構成は以下のようなイメージです。
server |
package main |
フューチャー技術ブログのfaviconを試しに読み込んでみました、無事に表示されています。
ちなみに変数publicとしてファイルシステムの階層を適宜掘り下げたものを用意しないと読み込んだディレクトリがリンクとして表示されてしまいます。
実際に開発を行う際はginやecho等のWebフレームワークを理想されるケースが一般的と思われますが、それらAPIでも同様の事が可能です。
Webサーバーに話が寄ってしまいましたが、設定やバージョン情報等の管理をgo:embedを使って運用していくような事が期待できます。
go:embedが使えないケース
go:embedでの埋め込みは関数の内部など閉じたスコープで行うことができません。
必然的に広いスコープで扱いたい設定情報等が用途として想定されます。
package main |
実行すると下記のようなエラーが発生します。
go run .\main.go |
go:embedによって何が嬉しいのか
記事の序盤でも書きましたが、単純に外部ファイルを読み込むだけならosやioutilでも行うことが可能です。go:embedで読み込んだファイルはビルドされたバイナリにも埋め込まれる、という点がその他の読み込み方法との決定的な違いになります。
最初に書いたサンプルコードのosバージョンを作りました。
package main |
jsonファイルが適切に配置されていれば、同様にjsonファイルの中身が出力されます。
go run .\main.go |
では、go:embedを利用したものと、osを利用したもので、ビルドした実行バイナリの挙動の違いを確認してみます。
osを利用したものでは、jsonファイルを削除してビルドしたバイナリを実行した場合、エラーが発生します。
.\main.exe |
一方、最初に紹介したgo:embedのサンプルコードは、jsonファイルを削除しても問題なく動作します。
.\main.exe |
go:embedで読み込んだファイルはビルドされたバイナリにも埋め込まれる、の意味がこのような挙動から体験できました。
これはGoの利点の1つである、単一の実行ファイルとしてビルドできることで、展開先の依存関係をシンプルに保つことができるという利点を強力に後押しします。設定ファイルや各種アセットをビルドに含めることで、バージョン管理やリリース作業を一層シンプルに整理できることが期待できます。
先ほど紹介した簡易Webサーバーで例えると、WebサーバーとコンテンツとなるHTML、CSS、JavaScriptが分離している場合、ローカル環境で動いたものを実際の環境にデプロイする場合、実行バイナリと各種アセットをデプロイ対象の環境で適宜整理する必要があります。
これらを全て単一のバイナリに含めることができた場合、作業は実行バイナリを1つコピーして起動するだけになります。
新しいサーバーにデプロイする際の運用フローの整備や、プロダクション向けの構成でコンテナを構築するDockerfileを書いていく事を考えると、go:embedで極限まで簡略化できる部分が想像できるかもしれません。
まとめ
go:embedは外部ファイルを読み込むことができるパッケージです。単一のファイルの中身を簡単に読み込めます、ファイルシステムを提供することも可能です。ローカル変数で利用することは現段階ではできません。
今までのGoでは実行ファイルとアセットファイルに分割されてしまっていた部分を、1つにまとめることが可能になりました。
普段の業務でgo:embedで解決できる部分が無いか、ぜひ探してみていただければと思います。