WebAssembly was originally designed to give in-browser web applications a way to run portable, sandboxed, high-performance binaries. As WASM matures beyond the browser, new uses for the technology are emerging. Using WASM to build programmability and extensibility into applications is one use case that is gathering steam.
The Extism software library lets you write programs that can interface with extensions written in WebAssembly. Extism handles the data and function-calling interface between code written in your host application and the WASM extensions. This lets you focus on writing the functionality in your application and extensions, rather than dealing manually with WASM’s data types or calling conventions.
Writing an Extism-powered app
The Extism library works with just about any widely-used language. Currently, it supports C/C++, Java, JavaScript, Go, Rust, Ruby, Python, the .NET language family (C# and F#, in particular), Elixir, PHP, OCaml, Zig, Haskell, and D. Since the library itself is written in Rust and exposes a C-types interface, any language with a C foreign-function interface can support Extism with a little work.
Plugins, or WASM extensions, can be written in any language that compiles to WASM. Rust is an obvious choice, being the language Extism is written in, but developers can use AssemblyScript (which compiles directly to WASM), or JavaScript, Go, C#, F#, C, Haskell, or Zig.
Extism’s philosophy is to support writing programs in such a way that their functionality can be freely extended with plugins written in WASM. This extensibility can be as shallow or deep as you like. Extism’s WASM runtime also automatically handles things like sandboxing execution and memory access. As such, it provides more process protection than other solutions you might use to expand a program’s functionality, like embedding a Lua interpreter.
Example of an Extism plugin
Here’s a simple example of an Extism plugin and a host application that uses it. The plugin has a single function, greet
, which takes in a string and returns a greeting using the supplied string. This incarnation of the plugin (also adapted from Extism’s docs) is written in AssemblyScript for simplicity:
import { Host } from '@extism/as-pdk';
export function greet(): i32 {
let name = Host.inputString();
let output = `Hello, ${name}!`;
Host.outputString(output);
return 0;
}
The Extism plugin development kit, or PDK, supplies objects we use to create interfaces with the outside world. The Host
object contains various abstractions for taking input from the host and returning it in different formats—in this case, strings. Another option is to take in and return JSON, since that’s a convenient way to handle structured data in Extism, but strings work for this example.
We also need to define which functions in our plugin are available externally. This varies between languages, but in AssemblyScript it’s done through the export
keyword. We could also define advanced error-handling features—for instance, a function to call if the plugin itself throws an error—but we’ll leave that out for simplicity.
Example of an Extism app
To write an application that uses an Extism plugin, you use the Extism library for the language you’re using to write the application. For this example, we’ll use Python, since both the language and the way Extism works with it are quite simple.
Here’s the Python program we’re using to work with this plugin:
import extism
manifest = {"wasm": [{"path":"myplugin/example.wasm"}]}
plugin = extism.Plugin(manifest)
def greet(text: str):
return plugin.call("greet", text)
print(greet("Jeffrey"))
Run this and you’ll get back the response: Hello, Jeffrey!
To use our plugin, we need to create a manifest—a Python dictionary, in this case—that describes where the plugin can be found (here, it’s in a project subdirectory called myplugin
). Manifests can also be used to describe other behaviors, like how much memory the plugin can allocate, or what paths on disk it is permitted to use.
Once we create an object to represent the plugin, we can make function calls to it using the .call()
method.
Note that this simple example only works for a single, predetermined plugin. If your application wants to accept arbitrary plugins, it’ll need to define interfaces to hook the plugins into. It can then either discover the presence of plugins automatically or use some kind of user-supplied metadata.
Conclusion
Extism, like WASM itself, is relatively young and its best use cases are still evolving. With its low barrier to entry for both writing WASM plugins and creating applications that use them, Extism is a useful way to experiment with WASM outside the browser. You’ll reap the rewards in discovery and dividends.