はじめに
8月16日にGo 1,17がリリースされました。今回の記事では一つ前のバージョンであるGo 1.16とGo 1.17での変更点と、その中でも
go:embed
について紹介します。
変更点一覧
Go 1.16
- Apple Silicon対応
-
runtime/metrics
パッケージの導入(runtime
やdebug
などのメトリクス収集機能が集約された) -
go:embed
の追加(本稿で紹介) -
io/fs
とio/ioutil
の非推奨化(io/ioutil
パッケージの機能がos
パッケージとio
パッケージへ移行された) -
go install
の新機能追加(バージョンが指定可能になり、go.mod
の書き換えなしでツールなどがインストール可能になった) -
single.NotifyContext
関数の追加
Go 1.17
- コンパイラの高速化
-
go mod
関連(必要な依存関係が全て含まれるように。Goバージョンの更新、上書きが可能になど) -
go get
関連(モジュール外からのインストール時に警告。-insecure
フラグが使えなくなり、代わりにGOINSECURE
を使うように) -
testing
に新機能追加(shuffle
オプションの追加。テストコード、ベンチマーク中に環境変数をセットできるようになった)
go:embedとは
go:embed
は実行ファイルにファイルを埋め込むことができるようにするパッケージです。
例えば、これまではDBのユーザやパスワードなど情報をTOMLファイルなので形式で保存した場合、実行ファイルと一緒にサーバ上に配置し、
io
や
ioutil
で読み込む必要がありましたが、
go:embed
を使うことで、実行ファイルを配置するだけよくなります。
go:embedの使い方
基本的な使い方
まずはサンプルコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package main import ( _ "embed" "fmt" ) var ( //go:embed sample.txt sample1 string //go:embed sample.txt sample1Bytes []byte ) func main() { fmt.Printf("sample1 as string: %s\n", sample1) fmt.Printf("sample1 as binary: %#v\n", sample1Bytes) } |
import _ "embed"
でパッケージをインポートします。この際、単一のファイルを埋め込むのであれば、先頭に
_
をつけてインポートすることが推奨されています。
1 2 3 4 | import ( _ "embed" "fmt" ) |
//go:embed xxxx.yyy
の形式(xxxx.yyyは埋め込むファイル名)でファイルの場所を記述します。
1 2 3 4 5 6 7 | var ( //go:embed sample.txt sample1 string //go:embed sample.txt sample1Bytes []byte ) |
必要な記述はこれだけです。非常に簡単に利用できることがわかったかと思います。
複数のファイルを埋め込む
次に、複数のファイルを埋め込む方法を紹介します。サンプルコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package main import ( "embed" "fmt" ) // go:embed sample1.txt sample2.txt var files embed.FS func main() { s1, err := files.ReadFile("sample1.txt") fmt.Printf("%s\n", s1) s2, err := files.ReadFile("sample2.txt") fmt.Printf("%s\n", s2) } |
import "embed"
でパッケージをインポートします(複数のファイルを埋め込む場合は
_
をつけないでインポートします)。
1 2 3 4 | import ( "embed" "fmt" ) |
変数の型は
embed.FS
とし、ファイル名をスペース区切りですべて記述します(この場合は
sample1.txt
と
sample2.txt
)。
1 2 | // go:embed sample1.txt sample2.txt var files embed.FS |
埋め込んだファイルを読み込むには、ReadFile(“xxxx.yyyy”)関数で個別に読み込みます(xxxx.yyyは埋め込むファイル名)
1 2 3 4 5 6 7 | func main() { s1, err := files.ReadFile("sample1.txt") fmt.Printf("%s\n", s1) s2, err := files.ReadFile("sample2.txt") fmt.Printf("%s\n", s2) } |
異なるディレクトリのファイルを埋め込む
main.go
とは異なるディレクトリにあるファイルを埋め込む事もできます。
1 2 | // go:embed other/sample.txt var sample string |
ちなみに、カレントディレクトリを示す
./
は不要です。また、
../
で親ディレクトリに遡って読み込むことはできません。
ディレクトリ内のファイルを一括で埋め込む
ファイル名の指定にはワイルドカードを使うことができ、特定のディレクトリとサブディレクトリが再帰的に埋め込まれます。
1 2 | // go:embed other/* var files embed.FS |
埋め込んだファイルにアクセスするには、パスも含めて書く必要があります。
1 2 3 4 5 6 7 | func main() { s1, err := files.ReadFile("other/sample1.txt") fmt.Printf("%s\n", s1) s2, err := files.ReadFile("other/sample2.txt") fmt.Printf("%s\n", s2) } |
ルートディレクトリ以外のファイルで埋め込む場合
埋め込むファイルは、embedを記述するファイルが有る場所からの相対パスになります。例えば、
libs/hoge.go
の中でembedする場合、
libs/
以下のファイルのみ埋め込むことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package libs import ( "embed" "fmt" ) // go:embed sample_dir/* var files embed.FS func main() { // libs/sample_dir/sample1.txtを表示すうる s1, err := files.ReadFile("sample_dir/sample1.txt") fmt.Printf("%s\n", s1) } |
テキストファイル以外を埋め込む場合
テキスト以外のファイル(例えばJSON)は、一度
byte型
の配列で読み込んでから
Unmarshal
するなど、一工夫が必要になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package main import ( _ "embed" "encoding/json" "fmt" ) //go:embed users.json var usersBytes []byte type user struct { Name string `json:"Name"` Email string `json:"Email"` } func main() { var users user if err := json.Unmarshal(usersBytes, &users); err != nil { panic(err) } fmt.Printf("%+v\n", s) } |
go:embedの制限
go:embedでのファイルの埋め込みは、関数の内などのスコープでは行えません。
1 2 3 4 5 | func main() { // go:embed sample.txt var sample string fmt.Printf("%s\n", sample) } |
さいごに
Go 1.16とGo 1.17の変更点のなかで、go:embedについて紹介しました。