pomo_mondreganto
pomo_mondreganto

Reputation: 2074

Golang plugin type assertion

I'm writing a simple app which loads plugin in a predefined format. Example plugin is the following:

package main

import (
    "errors"
    "fmt"
    "strings"
)

var (
    ok        bool
    InvConfig = errors.New("invalid config")
)

type Processor struct {
    logEverything bool
}

func (p *Processor) Init(config map[string]interface{}) error {
    p.logEverything, ok = config["log_everything"].(bool)
    if !ok {
        return InvConfig
    }
    return nil
}

func (p *Processor) Process(buf []byte) []byte {
    if p.logEverything {
        fmt.Printf("Shouter got data: %v\n", buf)
    }
    return []byte(strings.ToUpper(string(buf)))
}

func GetProcessor() *Processor {
    return &Processor{}
}

I can't quite apprehend how to load such a structure in my main program. So, I declare an interface:

type Processor interface {
    Init(map[string]interface{}) error
    Process(buf []byte) []byte
}

Then I load "getter" function and try to cast it to a function returning interface to then call it:

p, err := plugin.Open(filepath)
if err != nil {
    logrus.Fatalf("Error opening plugin %s: %v", pluginName, err)
}
procGetterInter, err := p.Lookup("GetProcessor")
if err != nil {
    logrus.Fatalf("Error loading processor getter for plugin %s: %v", pluginName, err)
}

procGetter, ok := procGetterInter.(func() interface{})
if !ok {
    logrus.Fatalf("Error casting processor getter for plugin %s: %T", pluginName, procGetterInter)
}

But the cast fails with an error:

Error casting processor getter for plugin simple_shout: func() *main.Processor

If I return an actual instance (not a pointer) from GetProcessor and try to cast the function to the one returning Processor, I get the same result:

Error casting processor getter for plugin simple_shout: func() main.Processor

How to get a struct instance from plugin (therefore load the function returning it) and type-assert it's an expected interface in my case?

UPD: If I remove everything from Processor interface (that is, it becomes just an empty interface):

type Processor interface {}

And try to cast procGetterInter to a function returning a pointer to Processor interface:

procGetter, ok := procGetterInter.(func() *Processor)

I still get the same error:

plugin.Symbol is func() *main.Processor, not func() *main.Processor (types from different scopes)

Why doesn't it cast even to pointer to an empty interface?

Upvotes: 3

Views: 1438

Answers (2)

JVMATL
JVMATL

Reputation: 2122

TL;DR: Check out a full working demo here: https://github.com/jvmatl/go-plugindemo


The long, but (hopefully!) informative answer:

Plugins are tricky in several ways, and @icza's answer is totally correct, but to understand why it's correct and how it applies to your question, you need to understand that the flexible nature of go's interfaces does not apply to complex types.

You have probably already run across this in other contexts:

This is legal in Go:

    var a interface{}
    var b int
    a = b // yep, an int meets the spec for interface{} !

But this is not:

    var aa []interface{}
    var bb []int
    aa = bb // cannot use bb (type []int) as type []interface {} in assignment 

Similarly, with functions, this is legal:

    type Runner interface {
        Run()
    }

    type UsainBolt struct{}
    func (ub *UsainBolt) Run() {
        fmt.Println("Catch me if you can!")
    }

    var a Runner
    var b *UsainBolt
    a = b // Yep, a (pointer to) Usain Bolt is a runner!

But this is not:

    var aa func() Runner
    var bb func() *UsainBolt
    aa = bb // cannot use bb (type func() *UsainBolt) as type func() Runner in assignment


Now let's look at defined function types. This is where it gets really interesting:

    type RunnerGetter func() Runner

    var rg RunnerGetter
    rg = getUsain  // <-- Nope: doesn't compile: "cannot use getUsain (type func() *UsainBolt) as type RunnerGetter in assignment"

    rg = getRunner // <-- This *assignment* is allowed: getRunner is assignable to a type RunnerGetter

    var i interface{} = getRunner
    rg = i.(RunnerGetter) // compiles, but panics at runtime: "interface conversion: interface {} is func() main.Runner, not main.RunnerGetter"

In other words, the language is ok with assigning func getRunner() Runner to a variable of type RunnerGetter, but the type assertion fails, because the type assertion is asking: is this thing actually a variable of type RunnerGetter? And the answer is no, it's a func() Runner which is close, but not quite right, so we panic.

But this works:

    var rg RunnerGetter
    var i interface{}
    i = rg // after this assignment, i *is* a RunnerGetter
    rg = i.(RunnerGetter) // so this assertion passes.

Ok, with all that background out of the way, the issue is that the symbol you lookup from your plugin must be exactly the same type as your type assertion says it is, not just close-enough-to-allow-assignment.

As @icza stated, you have a couple of options:

Option 1: Quick and Dirty, gets the job done In your plugin

func GetGeneric() interface{} {
    return &Processor{}
}

In your main: (error-handling skipped for clarity)

    p, _ := plugin.Open(pluginFile)                  // load plugin
    newIntf, _ := p.Lookup("Getgeneric")             // find symbol

    newProc, _ := newIntf.(func() interface{})       // assert symbol to generic constructor
    shoutProc, _ := newProc().(processors.Processor) // call generic constructor, type assert the return value

    // Now use your new plugin!
    shoutProc.Init(map[string]interface{}{"log_everything": true}) 
    output := shoutProc.Process([]byte("whisper"))

Option 2: Cleaner, better if you have many plugins Declare the interface all you plugins have to meet in another package:

package processors
// Every plugin must be able to give me something that meets this interface
type Processor interface {
        Init(map[string]interface{}) error
        Process(buf []byte) []byte
}

In your plugin:

type ShoutProcessor struct {
        configured    bool
        logEverything bool
}

func NewProcessor() processors.Processor {
        return &ShoutProcessor{}
}

In your main:

    p, _ := plugin.Open(pluginFile)             // load plugin
    newProcIntf, _ := p.Lookup("NewProcessor")  // lookup constructor

    newProc, _ := newProcIntf.(func() processors.Processor) // assert the type of the func
    shoutProc := newProc() // call the constructor, get a new ShoutProcessor

    // ready to rock and roll!
    shoutProc.Init(map[string]interface{}{"log_everything": true})
    output := shoutProc.Process([]byte("whisper"))

Upvotes: 2

icza
icza

Reputation: 417462

The function inside the plugin has a signature:

func GetProcessor() *Processor

You lookup this symbol as an interface{} and you try to type-assert a value of type

func() interface{}

These types do not match because these function types have different return types. Spec: Function types:

A function type denotes the set of all functions with the same parameter and result types.

So you may only type assert the same function type, but the problem is that you can't refer to identifiers declared in the plugin (the function's return type is a custom type defined in the plugin).

So a simple solution is to move the type declaration to another package, a common package that will be used by both the plugin and the main app (that loads the plugin).

Another solution is to declare your function to return an interface{} value so you can type assert this function, and you can call it, and you will obtain a value of type interface{}. Then your main app may define an interface type holding the methods you're interested in, and in the main app you can type assert to this interface type.

See details and examples here: go 1.8 plugin use custom interface

Also see related questions:

Is it possible to share a custom data type between a go plugin and an application?

Plugin symbol as function return

Upvotes: 1

Related Questions