LoginSignup
6
4

More than 3 years have passed since last update.

Go1.16で追加されるembedとio/fsパッケージについてざっと調べた

Last updated at Posted at 2020-12-19

はじめに

これは Kyash Advent Calendar 2020 の20日目の記事です。

Go1.16ではバイナリへのファイルやコンテンツを埋め込む //go:embed ディレクティブが有効になったり、ファイルシステムへの共通interfaceを提供する io/fs などが追加されてます。

それぞれ、configファイルをバイナリに埋め込むときにサードパーティライブラリを使わずにGo自体の仕組みで埋め込めるようになっていたり、ファイルツリーの形式を抽象化して統一したinterfaceで扱えるようにしてあったりします。

とても便利そうな機能なので、リリースされたら早速使ってみたいので下調べしてみました。

(余談ですが ちょうど記事を書いてる途中にGo1.16 Betaがリリースされた のでめちゃくちゃタイミングが良かったです)

embedについて

draftはこちら
ドキュメントはこちら

draftから気になってた機能その1です。

機能はシンプルで、 embed パッケージをimportしつつ目的の変数に //go:embed target/file/path というコメントを追加するとその内容がビルド時に変数に埋め込まれる!というすぐれものです。

使うのもカンタンだしバイナリへのファイル埋め込みは全部これにしたいですね。

例として適当な config.json があったと想定してembedをつかって埋め込んでみます。
Go1.16 Betaを使うと手元でも試せます。

config.json
{"key1":"val1","key2":"val2"}
example_embed_bytes.go
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としてもうけとれます。

examble_embed_string.go
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はこちら
ドキュメントはこちら

draftから気になってた機能その2です。
こちらは1.16には入らないと思ってたのですが、beta版の内容を確認したら入ってたのでうれしいです。

これはどういうパッケージかというと、 io.Readerio.Writer の成功にならって、ファイルシステムツリーも抽象化しよう!というものです。

Interfaceは以下です

FS.go
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)
}

File.go
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 に渡してつかってみる例です。

example_http_fs.go
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/templatehtml/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 はまだまだつづきます、あしたの投稿もおたのしみに〜。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4