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

## はじめに
これは [Kyash Advent Calendar 2020](https://adventar.org/calendars/5745) の20日目の記事です。

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

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

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

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

## embedについて
[draftはこちら](https://go.googlesource.com/proposal/+/master/design/draft-embed.md)
[ドキュメントはこちら](https://tip.golang.org/pkg/embed)

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はこちら](https://go.googlesource.com/proposal/+/master/design/draft-iofs.md)
[ドキュメントはこちら](https://tip.golang.org/pkg/io/fs)

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

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

Interfaceは以下です

https://go.googlesource.com/go/+/refs/tags/go1.16beta1/src/io/fs/fs.go#20

```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)
}
```

https://go.googlesource.com/go/+/refs/tags/go1.16beta1/src/io/fs/fs.go#72

```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/template` や `html/template` パッケージも `fs.FS` を受け取れるようになってるようです。
いままでなら [ParseFiles()](https://tip.golang.org/pkg/text/template/#ParseFiles) で初期化していたものが、 [ParseFS()](https://tip.golang.org/pkg/text/template/#ParseFS) 
が使えるようになったそう。これも便利っぽい。

Go1.16では、このほかにもいくつかのパッケージが `fs.FS` に対応したようです！
一回取得した `fs.FS` を標準パッケージ間で統一したinterfaceの上で使い回せるのはすごくうれしいです！

## さいごに

embed, io/fsともに必要最低限かつあると助かるものが追加されて個人的にはとても嬉しいです！
Kyashでもいくつか使いたい部分ががあるので今からリリースが楽しみです。

Go1.16はほかにもM1 Macのサポートがされたり、 `go install` の挙動が整理されたり、いろいろと使いやすそうになっているので早く使ってみたいですね！

[Kyash Advent Calendar 2020](https://adventar.org/calendars/5745) はまだまだつづきます、あしたの投稿もおたのしみに〜。