はじめに
これは Kyash Advent Calendar 2020 の20日目の記事です。
Go1.16ではバイナリへのファイルやコンテンツを埋め込む //go:embed
ディレクティブが有効になったり、ファイルシステムへの共通interfaceを提供する io/fs
などが追加されてます。
それぞれ、configファイルをバイナリに埋め込むときにサードパーティライブラリを使わずにGo自体の仕組みで埋め込めるようになっていたり、ファイルツリーの形式を抽象化して統一したinterfaceで扱えるようにしてあったりします。
とても便利そうな機能なので、リリースされたら早速使ってみたいので下調べしてみました。
(余談ですが ちょうど記事を書いてる途中にGo1.16 Betaがリリースされた のでめちゃくちゃタイミングが良かったです)
embedについて
draftから気になってた機能その1です。
機能はシンプルで、 embed
パッケージをimportしつつ目的の変数に //go:embed target/file/path
というコメントを追加するとその内容がビルド時に変数に埋め込まれる!というすぐれものです。
使うのもカンタンだしバイナリへのファイル埋め込みは全部これにしたいですね。
例として適当な config.json
があったと想定してembedをつかって埋め込んでみます。
Go1.16 Betaを使うと手元でも試せます。
{"key1":"val1","key2":"val2"}
package main
import (
_ "embed"
"encoding/json"
"fmt"
"log"
)
//go:embed config.json
var configBytes []byte
type config struct {
Key1 string `json:"key1"`
Key2 string `json:"key2"`
}
func main() {
var c config
if err := json.Unmarshal(configBytes, &c); err != nil {
log.Fatal(err)
}
fmt.Printf("%#v\n", c)
}
ビルドして実行した結果は以下。ちゃんとよみこめてる!
$ go build -o embedtest
$ ./embedtest
main.config{Key1:"val1", Key2:"val2"}
バイト列以外にもstringとしてもうけとれます。
package main
import (
_ "embed"
"fmt"
)
//go:embed config.json
var configString string
func main() {
fmt.Printf("%#v\n", configString)
}
$ go build -o embedtest
$ ./embedtest
"{\"key1\":\"val1\",\"key2\":\"val2\"}\n"
すごい便利だし楽!
埋め込みの際には、Go1.16から抽象化された io/fs.FS
を実装してる embed.FS
という型でも受け取れます。
後述しますが、この型は標準パッケージ間ですでに色々なところで使えるようになっていて非常に便利です。
いままでは、なにがしかのサードパーティツールを利用して go generate
で埋め込み実行していたのが go build
でできるようになるので、ファイルの埋め込みに関して開発者が気を使う必要がほぼなくなります!うれしい!
(なんかエラー出ると思ったら go generate
忘れててファイル埋め込めてないやん、みたいな経験をしたのは僕だけではないはず)
io/fsについて
draftから気になってた機能その2です。
こちらは1.16には入らないと思ってたのですが、beta版の内容を確認したら入ってたのでうれしいです。
これはどういうパッケージかというと、 io.Reader
や io.Writer
の成功にならって、ファイルシステムツリーも抽象化しよう!というものです。
Interfaceは以下です
type FS interface {
// Open opens the named file.
//
// When Open returns an error, it should be of type *PathError
// with the Op field set to "open", the Path field set to name,
// and the Err field describing the problem.
//
// Open should reject attempts to open names that do not satisfy
// ValidPath(name), returning a *PathError with Err set to
// ErrInvalid or ErrNotExist.
Open(name string) (File, error)
}
type File interface {
Stat() (FileInfo, error)
Read([]byte) (int, error)
Close() error
}
要はいろいろなパッケージを使用し、いろいろな手段でファイル読み取りしていた部分を、読み取り専用の fs.File
interfaceとそれを取得するための fs.FS
interfaceを使うことで統一しよう!というわけですね。
(今までは text/template
などにおける ParseFiles
のようにパッケージごとに実装されてたりしていた)
なるほどそれは便利そう!
以下はembedで受け取ったFSを同じくGo1.16で新規に追加された http.FS
に渡してつかってみる例です。
package main
import (
"embed"
"log"
"net/http"
)
//go:embed config.json
var configFS embed.FS
func main() {
http.Handle("/", http.FileServer(http.FS(configFS)))
log.Fatal(http.ListenAndServe(":8080", nil))
}
$ go build -o fstest
$ ./fstest
サーバーが立ち上がって fs.FS
で渡したコンテンツが /
配下に配置されてることが確認できます。
$ curl localhost:8080/config.json
{"key1":"val1","key2":"val2"}
今回は試してませんが、 text/template
や html/template
パッケージも fs.FS
を受け取れるようになってるようです。
いままでなら ParseFiles() で初期化していたものが、 ParseFS()
が使えるようになったそう。これも便利っぽい。
Go1.16では、このほかにもいくつかのパッケージが fs.FS
に対応したようです!
一回取得した fs.FS
を標準パッケージ間で統一したinterfaceの上で使い回せるのはすごくうれしいです!
さいごに
embed, io/fsともに必要最低限かつあると助かるものが追加されて個人的にはとても嬉しいです!
Kyashでもいくつか使いたい部分ががあるので今からリリースが楽しみです。
Go1.16はほかにもM1 Macのサポートがされたり、 go install
の挙動が整理されたり、いろいろと使いやすそうになっているので早く使ってみたいですね!
Kyash Advent Calendar 2020 はまだまだつづきます、あしたの投稿もおたのしみに〜。