Introducing the New WasmEdge Rust SDK

Aug 02, 2022 • 5 minutes to read

With the release of WasmEdge 0.10.1, we're excited to announce a brand new release of WasmEdge Rust bindings: wasmedge-sdk v0.3.0 and wasmedge-sys v0.8.0. The links to their API documentation and source code can be found here.

If you are a Rust enthusiast, welcome to take part in the Bug Hunt to win a Nintendo Switch!

The following diagram shows the architecture of WasmEdge Rust bindings. The wasmedge-sys crate defines a group of low-level Rust APIs, which simply wrap WasmEdge C-API and provide the safe counterparts, while wasmedge-sdk defines a group of high-level application-oriented Rust APIs.

We recommended most application developers use the wasmedge-sdk crate. Therefore, this article will focus on wasmedge-sdk. If you are interested in the inner workings of WasmEdge low-level Rust APIs, you can also check out the wasmedge-sys source code.

The WasmEdge Rust bindings SDK enables Rust applications to embed WebAssembly functions or modules. That is especially useful for Rust-based cloud native or blockchain infrastructure software as they need to support untrusted user functions in a safe and efficient manner.

The main design goal for the wasmedge-sdk crate is to enable developers to easily and safely incorporate third-party code into their rust applications with WasmEdge. The wasmedge-sdk crate aims to provide Rust developers an ergonomic experience.

Let's look at a simple example!

Getting started with wasmedge-sdk

This example shows how to run WasmEdge host functions from a Rust program. The WasmEdge host functions also are written in Rust. You could also write WasmEdge functions in other programming languages, like C, TinyGo or JavaScript.

Here we use a simple hello world program to explain how wasmedge-sdk works. This example demonstrates how to complete the following items.

  1. load the third-party native function via the ImportObjectBuilder
  2. load the wasm module generated by the native function

For more examples of wasmedge-sdk, please checkout out here.

First, make sure you have installed Rust and WasmEdge on your local system. If you would like to use the WasmEdgeProcess plugin of WasmEdge, Linux is the only supported OS.

Next, get the wasmedge-sdk example from the WasmEdge repo.

$ git clone https://github.com/WasmEdge/WasmEdge.git
$ cd /bindings/rust/

Then, run the hello world example from the wasmedge-sdk file using the following command.

cargo run -p wamedge-sdk --example hello_world -- --nocapture

If the command runs successfully, you will see Hello, world! print out in the terminal, shown as the snapshot below.

Now let's dive into the code. The source code of the above Hello World example could be found here.

Let's start by getting all imports immediately so you can follow along.

// please add this feature if you're using rust of version < 1.63
// #![feature(explicit_generic_args_with_impl_trait)]

use wasmedge_sdk::{params, Executor, ImportObjectBuilder, Module, Store};
use wasmedge_sys::WasmValue;
use wasmedge_types::wat2wasm;

Define a native function and create an ImportObject

First, let's define a native function named say_hello_world that prints out Hello, World!.

fn say_hello_world(_inputs: Vec<WasmValue>) -> Result<Vec<WasmValue>, u8> {
    println!("Hello, world!");

    Ok(vec![])
}

To use the native function as an import function in the WasmEdge runtime, we need an ImportObject. wasmedge-sdk defines a ImportObjectBuilder, which provides a group of chaining methods used to create an ImportObject. So let's see how to do it.

// create an import module
let import = ImportObjectBuilder::new()
    .with_func::<(), ()>("say_hello", say_hello_world)?
    .build("env")?; 

Now, we have an import module named env which holds a host function say_hello. As you may notice, the names we used for the import module and the host function are exactly the same as the ones appearing in the wasm module. You can find the wasm module in next section.

Load a wasm module

Now, let's load a wasm module. wasmedge-sdk defines two methods in Module:

  • from_file loads a wasm module from a file and, meanwhile, validates the loaded wasm module.

  • from_bytes loads a wasm module from an array of in-memory bytes and, meanwhile, validates the loaded wasm module.

Here we choose Module::from_bytes method to load our wasm module from an array of in-memory bytes.

let wasm_bytes = wat2wasm(
    br#"
(module
    ;; First we define a type with no parameters and no results.
    (type $no_args_no_rets_t (func (param) (result)))

    ;; Then we declare that we want to import a function named "env" "say_hello" with
    ;; that type signature.
    (import "env" "say_hello" (func $say_hello (type $no_args_no_rets_t)))

    ;; Finally we create an entrypoint that calls our imported function.
    (func $run (type $no_args_no_rets_t)
    (call $say_hello))
    ;; And mark it as an exported function named "run".
    (export "run" (func $run)))
"#,
)?;

// loads a wasm module from the given in-memory bytes and returns a compiled module
let module = Module::from_bytes(None, &wasm_bytes)?;

Register import module and compiled module

To register a compiled module, we need to check if it has dependencies on some import modules. In the wasm module this statement (import "env" "say_hello" (func $say_hello (type $no_args_no_rets_t))) tells us that it depends on an import module named env. Therefore, we need to register the import module first before registering the compiled wasm module.

// create an executor
let mut executor = Executor::new(None, None)?;

// create a store
let mut store = Store::new()?;

// register the import module into the store
store.register_import_module(&mut executor, &import)?;

// register the compiled module into the store and get a module instance
let extern_instance = store.register_named_module(&mut executor, "extern", &module)?;

In the code above we use Executor and Store to register the import module and the compiled module. wasmedge-sdk also provides alternative APIs to do the same thing: Vm::register_import_module and Vm::register_module_from_bytes.

Run the exported function

Now we are ready to run the exported function.

// get the exported function "run"
let run = extern_instance
    .func("run")
    .ok_or_else(|| anyhow::Error::msg("Not found exported function named 'run'."))?;

// run host function
run.call(&mut executor, params!())?;

What's next

In the near future, wasmedge-sdk will focus on adding support for async calls to embedded Wasm and async host functions. The other important feature is to support complex interface types in wasmedge-sdk, allowing developers to pass complex data structures like strings and user-defined complex types to Wasm VM.

Contributing to wasmedge-sdk's development

The wasmedge-sdk is still under development, and looks forward to the community's feedback. The following items are that you may help with. Check out the rules here.

  • Give wasmedge-sdk a try and let us know how to improve. Also, let us know if you have any questions by raising a GitHub issue.
  • Bug report. Inevitably, we assume that there are some edge use cases that we don't cover. If you find one, feel free to raise an issue and let us know.
  • Check out the docs. Documentation is essential for open source software. If you find something wrong with the docs, don't hesitate to raise an issue or create a PR to fix it.
  • Request for features. The current features of wasmedge-sdk are from our users’ real needs. If you have one good idea, let us know by raising issues.

Join WasmEdge Discord Server to learn more.

WasmEdge 0.10.1Productlanguage bindingsRust
A high-performance, extensible, and hardware optimized WebAssembly Virtual Machine for automotive, cloud, AI, and blockchain applications