8月16日にGo 1,17がリリースされました。今回の記事では一つ前のバージョンであるGo 1.16とGo 1.17での変更点と、その中でもgo:embed
について紹介します。
Go 1.16
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
は実行ファイルにファイルを埋め込むことができるようにするパッケージです。
例えば、これまではDBのユーザやパスワードなど情報をTOMLファイルなので形式で保存した場合、実行ファイルと一緒にサーバ上に配置し、io
やioutil
で読み込む必要がありましたが、go:embed
を使うことで、実行ファイルを配置するだけよくなります。
まずはサンプルコードを見てください。
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"
でパッケージをインポートします。この際、単一のファイルを埋め込むのであれば、先頭に_
をつけてインポートすることが推奨されています。
import ( _ "embed" "fmt" )
//go:embed xxxx.yyy
の形式(xxxx.yyyは埋め込むファイル名)でファイルの場所を記述します。
var ( //go:embed sample.txt sample1 string //go:embed sample.txt sample1Bytes []byte )
必要な記述はこれだけです。非常に簡単に利用できることがわかったかと思います。
次に、複数のファイルを埋め込む方法を紹介します。サンプルコードを見てください。
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"
でパッケージをインポートします(複数のファイルを埋め込む場合は_
をつけないでインポートします)。
import ( "embed" "fmt" )
変数の型はembed.FS
とし、ファイル名をスペース区切りですべて記述します(この場合はsample1.txt
とsample2.txt
)。
// go:embed sample1.txt sample2.txt var files embed.FS
埋め込んだファイルを読み込むには、ReadFile(“xxxx.yyyy”)関数で個別に読み込みます(xxxx.yyyは埋め込むファイル名)
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
とは異なるディレクトリにあるファイルを埋め込む事もできます。
// go:embed other/sample.txt var sample string
ちなみに、カレントディレクトリを示す./
は不要です。また、../
で親ディレクトリに遡って読み込むことはできません。
ファイル名の指定にはワイルドカードを使うことができ、特定のディレクトリとサブディレクトリが再帰的に埋め込まれます。
// go:embed other/* var files embed.FS
埋め込んだファイルにアクセスするには、パスも含めて書く必要があります。
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/
以下のファイルのみ埋め込むことができます。
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
するなど、一工夫が必要になります。
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でのファイルの埋め込みは、関数の内などのスコープでは行えません。
func main() { // go:embed sample.txt var sample string fmt.Printf("%s\n", sample) }
Go 1.16とGo 1.17の変更点のなかで、go:embedについて紹介しました。