Go 1.8のpluginパッケージを試してみる

# はじめに

来年2月に正式リリース予定のGo 1.8にて、新たにpluginパッケージが仲間入りします。これはGoでプラグインの仕組みを実現するものです。

プラグインはGoで作成されたシェアードライブラリー（.so）ファイルであり、中身はmainパッケージでエクスポートされた（＝名前が大文字で始まる）変数と関数で構成されます。

このプラグインの実装を12月1日に公開されたGo 1.8 beta 1で試してみた時はLinuxとmacOSのみが対応していましたが、12月16日に公開されたGo 1.8 beta 2からはLinuxのみ対応となっています。（macOSはGo 1.9で対応予定）

ここではプラグインのコーディングの仕方をざっくりと紹介させて頂きます。詳細については[公式ベータサイトのpluginパッケージのドキュメント](https://golang.org/pkg/plugin/)をご覧ください。

# ファイル目線でのコーディングの流れ

ファイル目線でのコーディングの流れは、次のようになります。

1. プラグインのgoファイルを作成する。
2. プラグインをビルドしてsoファイル（シェアードライブラリー）を作成する。
3. プラグインを呼び出すgoファイルを作成して実行する。

## プラグインのgoファイルを作成

プラグインとして単純な計算を行うcalc.goを作成してみましょう。

```go:calc.go
// This package is simple calculator plugin.
package main

import (
        "fmt"
)

// Pv is value of Pow2().
var Pv int

func init() {
        fmt.Println("init() of calc.")
}

// Pow2 calculate Pv powered 2.
func Pow2() int {
        return Pv * Pv
}

// AddSub calculate x plus y, x minus y.
func AddSub(x, y int) (int, int) {
        return x + y, x - y
}
```

パッケージ名がmainのため、このまま`go build`をするとエラーになります。コンパイルが通るかどうか確認する時は、パッケージ名をmain以外に書き換えてから`go build`してください。動作確認後に**パッケージ名をmainに戻すこと**をお忘れなく！q@w@p

## プラグインをビルドしてsoファイルを作成

作成したcalc.goをビルドしてsoファイルを作成します。

```bash
go build -buildmode=plugin
```

この場合、calc.goが置かれてるディレクトリー名でsoファイルが作成されます。作成されるsoファイル名は`-o`オプションで明示的に指定することができます。

```bash
go build -buildmode=plugin -o calc.so calc.go
```

ファイル拡張子は「.so」のみが使えます。（pluginパッケージのソースコード内にて".so"かどうか判定しています。）

## プラグインを呼び出すgoファイルを作成

呼び出す側の処理の流れは、次のようになります。

1. プラグインを読み込む。（`plugin.Open()`）
2. プラグインの`init()`関数が実行される。
3. 読み込んだプラグインから変数や関数のシンボルを取得する。（`p.Lookup()`）
4. シンボルを経由してプラグインの変数や関数を呼び出す。（`symbol.()`）

プラグインを呼び出す側のmain.goを作成します。

```go:main.go
package main

import (
        "fmt"
        "os"
        "plugin"
)

func main() {
        // プラグイン（soファイル）を読み込む
        p, err := plugin.Open("calc.so")
        if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(-1)
        }

        // 変数Pvのシンボルを取得
        pv, err := p.Lookup("Pv")
        if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(-1)
        }


        // 関数Pow2()のシンボルを取得
        pow2, err := p.Lookup("Pow2")
        if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(-1)
        }

        // pvを2のべき乗した計算結果を出力
        *pv.(*int) = 3
        p2 := pow2.(func() int)()
        fmt.Printf("pow2: %d * %d = %d\n", *pv.(*int), *pv.(*int), p2)

        // 関数AddSub()のシンボルを取得
        addsub, err := p.Lookup("AddSub")
        if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(-1)
        }

        // 足し算と引き算の計算結果を出力
        x, y := 5, 2
        add, sub := addsub.(func(int, int) (int, int))(x, y)
        fmt.Printf("add: %d + %d = %d\n", x, y, add)
        fmt.Printf("sub: %d - %d = %d\n", x, y, sub)
}
```

変数では、プラグインを利用する側から設定した値をプラグインの内で使えること、関数では、複数の引き数や戻り値を取り扱えられることが確認できます。

## プラグインを呼び出すgoファイルを実行

main.goを実行して正常終了した場合、次の内容が出力されます。

```bash
$ go run main.go 
init() of calc.
pow2: 3 * 3 = 9
add: 5 + 2 = 7
sub: 5 - 2 = 3
```

# さいごに

本パッケージの登場でGoでシェアードライブラリーへ気軽にアクセスできるようになりました。利用シーンとしては、プロジェクトでの共通部品やプロダクトの機能拡張などがすぐに思い付きますが、よい活用方法がありましたらQiitaやブログなどでご紹介ください。

## 参考資料

- https://golang.org/pkg/plugin/
- https://jeremywho.com/go-1.8---plugins/
- http://inforno.net/articles/2016/12/16/gopher-lua-plugins-in-go18