Plugins in Go 1.8

Posted on ‐ Tagged #go #golang #plugins #programming

A look at the new plugin package coming in Go 1.8 - a bit of background, a look on how to make use of it and some of its (current) limitations.

Recent Go releases have introduced a number of different ways to compile Go code. With Go 1.5 for example came the option to link Go code into non-Go programs using a C-style API, allowing non-Go programs to run pieces of Go code. The same 1.5 release also introduced the option to build Go packages as shared libraries instead of single statically-linked binaries1.

(This can be useful if, for example, you use the same package(s) in a set of different applications and want a way to update them without having to recompile all those applications individually. Linux distros like Debian and Red Hat have traditionally made use of this a lot in their packaging for C/C++ applications for example.)

Go 1.8 will introduce yet another way to compile Go code, one that is likely more interesting to the average Go programmer than the other above-mentioned build modes. Coming with this release is the ability to build a shared library out of some Go code to then load and use this library from inside another Go program.

In other words, Go 1.8 will allow you to compile Go code as a plugin to be loaded into and used from another Go application.

Caveats

Before we look at how to use this latest new shiny though, here’s some caveats you should be aware of; This new plugin buildmode is currently only supported on Linux. Darwin support was originally included but it has since been pulled because of stability issues2 which cannot be resolved within the 1.8 release window.

Another limitation is the fact that plugins must be built with the same version of the Go toolchain as the main application3. That means you can’t compile a plugin with, say, a future Go version 1.9 and expect it to work with an application built with Go 1.8 or vice versa.

The new plugin package

So how does it all work in practice? In order to support this new plugin buildmode, the Go standard libray has gained a new package (unsurprisingly called plugin) and the go compiler now understands -buildmode=plugin.

To build a plugin with these there’s really not much special stuff you need to do. The only requirement here is that your plugin needs to be built from a main package4, such as the following plugin.go for example:

package main

// This import is needed even though you're not calling any C
// functions directly.
import "C"

// Greet returns a simple greeting.
func Greet() string {
	return "Hello world"
}

You can then build a plugin out of it by using the aforementioned -buildmode=plugin argument to go build, which will give you a shared object (.so) file:

➔  go build -buildmode=plugin plugin.go

➔  ls -l
total 1544
-rw-rw-r-- 1 zoni zoni      60 Jan  8 23:44 plugin.go
-rw-rw-r-- 1 zoni zoni 1576680 Jan  8 23:45 plugin.so

➔  file plugin.so
plugin.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=fc2ec03a4fca7e2b78aa1cb8bd82a9e1c99a0554, not stripped

In your main application you can now use the Open function from the plugin package to get a reference to this plugin and on that you can use Lookup to get a reference to any exported symbol, such as a variable or function:

package main

import (
	"fmt"
	"plugin"
)

func main() {
	p, err := plugin.Open("plugin.so")
	if err != nil {
		panic(err)
	}

	greetSymbol, err := p.Lookup("Greet")
	if err != nil {
		panic(err)
	}

	// Note that because the compiler cannot know in advance
	// what data type or function signature you are trying
	// to use, type assertions must be used.
	greet := greetSymbol.(func() string)
	fmt.Println(greet())
}

Compiling and running this program as usual should result in the string “Hello world” being printed:

➔  go run main.go 
Hello world

Conclusion

Plugins are simple to build and load into applications which means they are easily accessible to the average Go programmer - no magic build system required. True to form, the plugin package included in the standard library is trimmed down only to the bare essentials, leaving it up to developers to think about and implement suitable higher-level things such as plugin discovery themselves.

Adding plugin support to the language in this way will finally make it possible to extend applications with (possibly end-user supplied) plugins in a pretty straightward manner. It means embedding a lua or javascript runtime within a Go application is no longer the only viable option for building applications which should be extendable beyond the core application itself. This I find an exciting prospect with great opportunities.

With all that said though, it will probably be a while before plugins see much adoption and I currently would not recommend using them in any sizable project just yet. The current lack of support for any platform other than Linux means plugins will only be usable for a very limited target audience for a while to come.

Footnotes

  1. It may be worth noting this has been possible for longer using gccgo already. 

  2. CL 34391: cmd/go, plugin: disable plugins on darwin

  3. Go execuction modes, “Versioning”

  4. This requirement feels somewhat arbitrary to me but I’m sure there are good reasons for this design. I haven’t been able to find out why this is but I would love to know.