39 releases (16 stable)

1.10.0 Feb 10, 2025
1.9.1 Nov 26, 2024
1.8.0 Oct 22, 2024
1.5.0 Jul 23, 2024
0.1.0 Nov 30, 2022

#8 in WebAssembly

Download history 986/week @ 2024-10-30 836/week @ 2024-11-06 1097/week @ 2024-11-13 951/week @ 2024-11-20 1891/week @ 2024-11-27 830/week @ 2024-12-04 1247/week @ 2024-12-11 876/week @ 2024-12-18 777/week @ 2024-12-25 911/week @ 2025-01-01 1178/week @ 2025-01-08 984/week @ 2025-01-15 1449/week @ 2025-01-22 839/week @ 2025-01-29 1706/week @ 2025-02-05 1607/week @ 2025-02-12

5,796 downloads per month
Used in 26 crates (17 directly)

BSD-3-Clause

235KB
5.5K SLoC

Extism runtime and rust-sdk

This repo contains the code for the Extism runtime and rust-sdk. It can be embedded in any Rust application to call Extism plug-ins.

Note: If you're unsure what Extism is or what an SDK is see our homepage: https://extism.org.

Installation

Cargo

To use the extism crate, you can add it to your Cargo file:

[dependencies]
extism = "1.4.1"

Environment variables

There are a few environment variables that can be used for debugging purposes:

  • EXTISM_ENABLE_WASI_OUTPUT=1: show WASI stdout/stderr
  • EXTISM_MEMDUMP=extism.mem: dump Extism linear memory to a file
  • EXTISM_COREDUMP=extism.core: write coredump to a file when a WebAssembly function traps
  • EXTISM_DEBUG=1: generate debug information
  • EXTISM_PROFILE=perf|jitdump|vtune: enable Wasmtime profiling
  • EXTISM_CACHE_CONFIG=path/to/config.toml: enable Wasmtime cache, see the docs for details about configuration. Setting this to an empty string will disable caching.

Note: The debug and coredump info will only be written if the plug-in has an error.

Getting Started

This guide should walk you through some of the concepts in Extism and the extism crate.

Creating A Plug-in

The primary concept in Extism is the plug-in. You can think of a plug-in as a code module stored in a .wasm file.

Since you may not have an Extism plug-in on hand to test, let's load a demo plug-in from the web:

use extism::*;

fn main() {
  let url = Wasm::url(
    "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm"
  );
  let manifest = Manifest::new([url]);
  let mut plugin = Plugin::new(&manifest, [], true).unwrap();
  let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
  println!("{}", res);
}

Note: See the Manifest docs as it has a rich schema and a lot of options.

Calling A Plug-in's Exports

This plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one "export" function: count_vowels. We can call exports using Extism::Plugin::call:

let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
println!("{}", res);
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}

All exports have a simple interface of bytes-in and bytes-out. This plug-in happens to take a string and return a JSON encoded string with a report of results.

The call function uses extism-convert to determine which input/output types can be used. If we wanted to use a concrete type for the count_vowels result, we could defined a struct:

#[derive(Debug, serde::Deserialize)]
struct VowelCount {
  count: usize,
  total: usize,
  vowels: String,
}

Then we can use Json to get the JSON results decoded into VowelCount:

let Json(res) = plugin.call::<&str, Json<VowelCount>>("count_vowels", "Hello, world!").unwrap();
println!("{:?}", res);
# => VowelCount {count: 3, total: 3, vowels: "aeiouAEIOU"}

Plug-in State

Plug-ins may be stateful or stateless. Plug-ins can maintain state b/w calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. You can see this by making subsequent calls to the export:

let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
println!("{}", res);
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}

let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
println!("{}", res);
# => {"count": 3, "total": 9, "vowels": "aeiouAEIOU"}

These variables will persist until this plug-in is freed or you initialize a new one.

Configuration

Plug-ins may optionally take a configuration object. This is a static way to configure the plug-in. Our count-vowels plugin takes an optional configuration to change out which characters are considered vowels. Example:

let manifest = Manifest::new([url]);
let mut plugin = Plugin::new(&manifest, [], true);
let res = plugin.call::<&str, &str>("count_vowels", "Yellow, world!").unwrap();
println!("{}", res);
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
let mut plugin = Plugin::new(&manifest, [], true).with_config_key("vowels", "aeiouyAEIOUY");
let res = plugin.call::<&str, &str>("count_vowels", "Yellow, world!").unwrap();
println!("{}", res);
# => {"count": 4, "total": 4, "vowels": "aeiouyAEIOUY"}

Host Functions

Let's extend our count-vowels example a little bit: Instead of storing the total in an ephemeral plug-in var, let's store it in a persistent key-value store!

Wasm can't use our KV store on it's own. This is where Host Functions come in.

Host functions allow us to grant new capabilities to our plug-ins from our application. They are simply some Rust functions you write which can be passed down and invoked from any language inside the plug-in.

Let's load the manifest like usual but load up this count_vowels_kvstore plug-in:

let url = Wasm::url(
  "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm"
);
let manifest = Manifest::new([url]);

Note: The source code for this is here and is written in rust, but it could be written in any of our PDK languages.

Unlike our previous plug-in, this plug-in expects you to provide host functions that satisfy our its import interface for a KV store.

We want to expose two functions to our plugin, kv_write(key: String, value: Bytes) which writes a bytes value to a key and kv_read(key: String) -> Bytes which reads the bytes at the given key.

use extism::*;

// pretend this is redis or something :)
type KVStore = std::collections::BTreeMap<String, Vec<u8>>;

// When a first argument separated with a semicolon is provided to `host_fn` it is used as the
// variable name and type for the `UserData` parameter
host_fn!(kv_read(user_data: KVStore; key: String) -> u32 {
    let kv = user_data.get()?;
    let kv = kv.lock().unwrap();
    let value = kv
        .get(&key)
        .map(|x| u32::from_le_bytes(x.clone().try_into().unwrap()))
        .unwrap_or_else(|| 0u32);
    Ok(value)
});

host_fn!(kv_write(user_data: KVStore; key: String, value: u32) {
    let kv = user_data.get()?;
    let mut kv = kv.lock().unwrap();
    kv.insert(key, value.to_le_bytes().to_vec());
    Ok(())
});

fn main() {
    let kv_store = UserData::new(KVStore::default());

    let url = Wasm::url(
        "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm",
    );
    let manifest = Manifest::new([url]);
    let mut plugin = PluginBuilder::new(manifest)
        .with_wasi(true)
        .with_function(
            "kv_read",
            [PTR],
            [PTR],
            kv_store.clone(),
            kv_read,
        )
        .with_function(
            "kv_write",
            [PTR, PTR],
            [],
            kv_store.clone(),
            kv_write,
        )
        .build()
        .unwrap();

    for _ in 0..5 {
        let res = plugin
            .call::<&str, &str>("count_vowels", "Hello, world!")
            .unwrap();
        println!("{}", res);
    }
}

Note: In order to write host functions you should get familiar with the methods on the CurrentPlugin and UserData types.

Now we can invoke the event:

let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
println!("{}", res);
# => Read from key=count-vowels"
# => Writing value=3 from key=count-vowels"
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}

let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
println!("{}", res);
# => Read from key=count-vowels"
# => Writing value=6 from key=count-vowels"
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}

Dependencies

~24–39MB
~655K SLoC

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy