Я сталкиваюсь с поведением Go, которого не понимаю. Моя идея состоит в том, чтобы импортировать плагин, который реализует интерфейс, не входящий в оба пакета. Если возвращается структура, она работает нормально, но чтобы убедиться, что она реализует интерфейс, я хочу вернуть интерфейс, который не работает.
Определение интерфейса:
package iface
type IPlugin interface{
SayHello(string)
SayGoodby(string)
WhatsYourName() string
}
Основная программа выглядит так:
package main
import (
"plugin"
"plugin_test/iface"
"errors"
"fmt"
)
//go:generate go build -buildmode=plugin -o ./pg/test.so ./pg/test.go
func main(){
path := "pg/test.so"
plug, err := plugin.Open(path)
if err != nil {
panic(err)
}
sym, err := plug.Lookup("Greeter")
if err != nil {
panic(err)
}
var pg iface.IPlugin
pg, ok := sym.(iface.IPlugin)
if !ok {
panic(errors.New("error binding plugin to interface"))
}
fmt.Printf("You're now connected to: %s \n", pg.WhatsYourName())
pg.SayHello("user")
pg.SayGoodby("user")
}
Плагин (хранится в pg / test.go)
package main
import (
"fmt"
"plugin_test/iface"
)
type testpl struct {}
func(pl testpl) SayHello(s string){
fmt.Printf("Plugin says hello to %s \n", s)
}
func(pl testpl) SayGoodby(s string){
fmt.Printf("Plugin says goodby to %s \n", s)
}
func(pl testpl) WhatsYourName() string{
return "my name is Test-Plugin"
}
/* This function works */
func getPlugin() testpl{
return testpl{}
}
/* This function doesn't work */
func getPlugin() iface.IPlugin{
return testpl{}
}
/* This function also doesn't work */
func getPlugin() interface{}{
return testpl{}
}
var Greeter = getPlugin()
Я пробовал каждую функцию getPlugin по отдельности.
Функция, возвращающая testpl, печатает ожидаемый результат:
You're now connected to: my name is Test-Plugin
Plugin says hello to user
Plugin says goodby to user
Остальные функции заканчиваются на sym.(iface.IPlugin).
panic: error binding plugin to interface
goroutine 1 [running]:
main.main()
/home/../../../main.go:27 +0x343
exit status 2
Может кто-нибудь объяснить, почему это невозможно? Разве не было бы проще создать плагин, если бы он не позволял вам создавать свой плагин в таком случае?

То, что вы хотите, возможно, но что-то в фоновом режиме мешает этому.
Это именно то, что вы хотите найти в плагине Переменная с именем Greeter. Plugin.Lookup() вернет указатель на эту переменную! В противном случае вы могли бы только проверить его значение, но вы не могли его изменить.
Вы можете проверить это, просто распечатав тип значения, хранящегося в sym:
fmt.Printf("%T\n", sym)
В вашем первом случае func getPlugin() testpl вывод будет:
*main.testpl
Во втором случае func getPlugin() iface.IPlugin вывод будет:
*iface.IPlugin
(Да, это указатель на интерфейс!)
В третьем случае func getPlugin() interface{} вывод будет:
*interface {}
Итак, ваш первый пример работает, потому что значение, хранящееся в sym, относится к типу *main.testpl, который также реализует iface.IPlugin (поскольку main.testpl реализует его, также как и тип указателя).
Вернемся к вашему 2-му примеру: func getPlugin() iface.IPlugin
Значение, хранящееся в sym, относится к типу *iface.IPlugin. Значение типа указателя на интерфейс никогда не удовлетворяет никаким интерфейсам (кроме пустого интерфейса), поэтому попытка утверждения типа iface.IPlugin из значения типа *iface.IPlugin никогда не увенчается успехом. Вам необходимо ввести assert типа *iface.IPlugin, после чего вы можете разыменовать его, чтобы получить значение типа iface.IPlugin. Это могло выглядеть так:
pgPtr, ok := sym.(*iface.IPlugin)
if !ok {
panic(errors.New("error binding plugin to interface"))
}
pg := *pgPtr
И теперь все работает как положено!
Чтобы избежать таких хлопот и путаницы, вы можете реализовать свой плагин, чтобы предоставить функция, который возвращает вам Greeter:
func Greeter() iface.IPlugin { return testpl{} }
И затем, конечно же, избавьтесь от глобальной переменной Greeter. Если вы сделаете это, вы можете найти символ Greeter, который будет иметь тип:
func() iface.IPlugin
Разница в том, что для поиска функции не требуется, чтобы пакет plugin возвращал указатель на значение, тогда как в случае переменной это так. Простой тип функции, без указателя на интерфейс-кунг-фу. Использование его для получения приветствующего будет:
Greeter, err := p.Lookup("Greeter")
if err != nil {
panic(err)
}
greeterFunc, ok := GetFilter.(func() iface.IPlugin)
if !ok {
panic(errors.New("not of expected type"))
}
greeter := greeterFunc()
// And using it:
fmt.Printf("You're now connected to: %s \n", greeter.WhatsYourName())
greeter.SayHello("user")
greeter.SayGoodby("user")
См. Связанный / похожий вопрос: плагин go 1.8 использует настраиваемый интерфейс
Спасибо за подробное объяснение. Решение о возврате с помощью публичной функции кажется намного красивее, чем глобальная переменная. Я буду использовать этот метод в будущем.