カテゴリー: BackEnd

go:embedとGo 1.16、1.17での変更点まとめ

はじめに

8月16日にGo 1,17がリリースされました。今回の記事では一つ前のバージョンであるGo 1.16とGo 1.17での変更点と、その中でもgo:embedについて紹介します。

変更点一覧

Go 1.16

  • Apple Silicon対応
  • runtime/metricsパッケージの導入(runtimedebugなどのメトリクス収集機能が集約された)
  • go:embedの追加(本稿で紹介)
  • io/fsio/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ファイルなので形式で保存した場合、実行ファイルと一緒にサーバ上に配置し、ioioutilで読み込む必要がありましたが、go:embedを使うことで、実行ファイルを配置するだけよくなります。

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.txtsample2.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の制限

go:embedでのファイルの埋め込みは、関数の内などのスコープでは行えません。

func main() {
    // go:embed sample.txt
    var sample string
    fmt.Printf("%s\n", sample)
}

さいごに

Go 1.16とGo 1.17の変更点のなかで、go:embedについて紹介しました。

おすすめ書籍

Hiroki Ono

シェア
執筆者:
Hiroki Ono
タグ: golang

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

4週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

1か月 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前