golangでのembedの話

  • #開発
  • #embed
  • #golang

こんにちは。CURUCURUエンジニアの長尾です。 今回はgolangにおけるembedについてお話します。


embedパッケージとは?

go1.16から使えるパッケージです。 ビルドされたバイナリファイルの中に直接ファイルを「埋め込む」ことができます。

埋め込むとは?

「埋め込む」というのはどういうことでしょうか? 通常のファイル読み込みとの違いはなんでしょうか?

実際にコードを見ていったほうが早いので以下を見てください。

通常のioパッケージを利用したファイル読み込み

package main

import (
	"fmt"
	"io"
	"os"
)

func main() {
	f, err := os.Open("sample.txt")
	if err != nil {
		return
	}
	txt, err := io.ReadAll(f)
	if err != nil {
		return
	}
	fmt.Println(string(txt))
}

sample.txtが存在している状態で実行すれば正常にテキストが表示されますが、 このコードをビルドした後にsample.txtを削除するとテキストは表示されません。 なぜなら、os.Openでは実行時にファイルを読み込むためです。

embedを使ったファイル読み込み(単一ファイル埋め込み)

package main

import (
	_ "embed"
	"fmt"
)

//go:embed sample.txt
var sampleText []byte

func main() {
	fmt.Println(string(sampleText))
}

embedを使って埋め込んだ場合、読み出し処理が不要なためコードが非常に簡潔になります。 また、ビルドした後にsample.txtを削除してもファイルがビルドに埋め込まれているため正常に動作します。

埋め込み方法

embedを使った埋め込みには

  • 単一ファイル埋め込み
  • embed.FSを利用した埋め込み があります。

embed.FSを使った埋め込み

main.go
┗ data
    ┣ sample1.json
    ┗ sample2.json

sample1.jsonの中身
{
    "date": "2023-08-01",
    "weather": "sunny"
}
package main

import (
	"embed"
	"encoding/json"
	"fmt"
)

//go:embed data/*
var sampleJson embed.FS

type Weather struct {
	Date    string `json:"date"`
	Weather string `json:"weather"`
}

func main() {
	var weather Weather
	data, err := sampleJson.ReadFile("data/sample1.json")
	if err != nil {
		return
	}
	json.Unmarshal(data, &weather)

	fmt.Println(weather.Date, weather.Weather)
}

気をつけるポイント

  • //go:embedの記述は「//」と「go:embed」の間にスペース不要。スペースを入れると通常のコメント扱いになってしまいます。
  • ファイル対象に制限はなし。画像、json、htmlなど読み込み可能です。
  • 埋め込んだファイルの内容がビルド後に変更されても、それはビルドバイナリには反映されないです。

利用用途

go:embedに関してのイメージはついたかと思いますが用途はどうでしょうか? ビルド時に埋め込まれるのは便利な反面、ビルド後にファイル変更に追従できないのはデメリットのように思えます。

弊社では「頻繁に変更のないファイル」に関して利用しています。

例えば、弊社のサービスからメール送信する際のメールのテンプレートとしてembedを使い埋め込みを行っています。 embedのいいところは一度ビルドが通ってしまえばその後は正常動作が保障されているところですね。

まとめ

今回はgoのembedについてお話しました。 以上、長尾がお届けしました。

メンバー募集

CURUCURUでは開発メンバーを募集中です。 CURUCURUの開発に興味があったり、モダンな開発環境で挑戦してみたいという方がいましたら、ぜひこちらも覗いてみてください!